geobox 2.2.6__py3-none-any.whl → 2.4.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/api.py +265 -6
- geobox/aio/apikey.py +0 -26
- geobox/aio/attachment.py +5 -31
- geobox/aio/basemap.py +2 -30
- geobox/aio/dashboard.py +0 -26
- geobox/aio/feature.py +172 -17
- geobox/aio/field.py +0 -27
- geobox/aio/file.py +0 -26
- geobox/aio/layout.py +0 -26
- geobox/aio/log.py +0 -27
- geobox/aio/map.py +0 -26
- geobox/aio/model3d.py +0 -26
- geobox/aio/mosaic.py +0 -26
- geobox/aio/plan.py +0 -26
- geobox/aio/query.py +0 -26
- geobox/aio/raster.py +0 -26
- geobox/aio/scene.py +1 -26
- geobox/aio/settings.py +0 -28
- geobox/aio/table.py +1733 -0
- geobox/aio/task.py +0 -27
- geobox/aio/tile3d.py +0 -26
- geobox/aio/tileset.py +1 -26
- geobox/aio/usage.py +0 -32
- geobox/aio/user.py +0 -59
- geobox/aio/vector_tool.py +49 -0
- geobox/aio/vectorlayer.py +8 -34
- geobox/aio/version.py +1 -25
- geobox/aio/view.py +5 -35
- geobox/aio/workflow.py +0 -26
- geobox/api.py +265 -5
- geobox/apikey.py +0 -26
- geobox/attachment.py +0 -26
- geobox/basemap.py +0 -28
- geobox/dashboard.py +0 -26
- geobox/enums.py +15 -1
- geobox/feature.py +170 -15
- geobox/field.py +20 -37
- geobox/file.py +0 -26
- geobox/layout.py +0 -26
- geobox/log.py +0 -26
- geobox/map.py +0 -26
- geobox/model3d.py +0 -26
- geobox/mosaic.py +0 -26
- geobox/plan.py +1 -26
- geobox/query.py +1 -26
- geobox/raster.py +1 -31
- geobox/scene.py +1 -27
- geobox/settings.py +1 -29
- geobox/table.py +1719 -0
- geobox/task.py +2 -29
- geobox/tile3d.py +0 -26
- geobox/tileset.py +1 -26
- geobox/usage.py +2 -33
- geobox/user.py +1 -59
- geobox/vector_tool.py +49 -0
- geobox/vectorlayer.py +9 -36
- geobox/version.py +1 -26
- geobox/view.py +4 -34
- geobox/workflow.py +1 -26
- {geobox-2.2.6.dist-info → geobox-2.4.0.dist-info}/METADATA +2 -2
- geobox-2.4.0.dist-info/RECORD +74 -0
- {geobox-2.2.6.dist-info → geobox-2.4.0.dist-info}/WHEEL +1 -1
- geobox-2.2.6.dist-info/RECORD +0 -72
- {geobox-2.2.6.dist-info → geobox-2.4.0.dist-info}/licenses/LICENSE +0 -0
- {geobox-2.2.6.dist-info → geobox-2.4.0.dist-info}/top_level.txt +0 -0
geobox/aio/table.py
ADDED
|
@@ -0,0 +1,1733 @@
|
|
|
1
|
+
from typing import List, Dict, Optional, TYPE_CHECKING, Union, Any
|
|
2
|
+
from urllib.parse import urljoin
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .base import AsyncBase
|
|
6
|
+
from .task import AsyncTask
|
|
7
|
+
from ..enums import FieldType, RelationshipCardinality, TableExportFormat
|
|
8
|
+
from ..exception import NotFoundError
|
|
9
|
+
from ..utils import clean_data
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from . import AsyncGeoboxClient
|
|
13
|
+
from .user import AsyncUser
|
|
14
|
+
from .file import AsyncFile
|
|
15
|
+
from ..table import Table, TableField
|
|
16
|
+
from .vectorlayer import AsyncVectorLayer
|
|
17
|
+
from .field import AsyncField
|
|
18
|
+
from .feature import AsyncFeature
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class RelationshipEndpoint:
|
|
23
|
+
"""
|
|
24
|
+
Represents one endpoint (source or target) of a relationship
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
table (AsyncTable | AsyncVectorLayer): The source or target table or vector layer
|
|
28
|
+
field (AsyncTableField | AsyncField | str): The field name or object on the source/target entity
|
|
29
|
+
fk_field (AsyncTableField | AsyncField | str, optional): The foreign key field name or object on the relation table. (Required for Many-to-Many relationships)
|
|
30
|
+
"""
|
|
31
|
+
table: Union['AsyncTable', 'AsyncVectorLayer']
|
|
32
|
+
field: Union['AsyncTableField', 'AsyncField', str]
|
|
33
|
+
fk_field: Optional[Union['AsyncTableField', 'AsyncField', str]] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AsyncTableRow(AsyncBase):
|
|
37
|
+
|
|
38
|
+
def __init__(self,
|
|
39
|
+
table: 'AsyncTable',
|
|
40
|
+
data: Optional[Dict] = {},
|
|
41
|
+
):
|
|
42
|
+
"""
|
|
43
|
+
Constructs all the necessary attributes for the AsyncTableRow object.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
table (AsyncTable): The table that the row belongs to.
|
|
47
|
+
data (Dict, optional): The data of the field.
|
|
48
|
+
"""
|
|
49
|
+
super().__init__(api=table.api, data=data)
|
|
50
|
+
self.table = table
|
|
51
|
+
self.endpoint = urljoin(table.endpoint, f'rows/{self.id}/') if self.data.get('id') else None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Return a string representation of the AsyncTableRow.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
str: The string representation of the AsyncTableRow.
|
|
60
|
+
"""
|
|
61
|
+
return f"AsyncTableRow(id={self.id}, table_name={self.table.data.get('name', 'None')})"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
async def create_row(cls, table: 'AsyncTable', **kwargs) -> 'AsyncTableRow':
|
|
66
|
+
"""
|
|
67
|
+
[async] Create a new row in the table.
|
|
68
|
+
|
|
69
|
+
Each keyword argument represents a field value for the row, where:
|
|
70
|
+
- The keyword is the field name
|
|
71
|
+
- The value is the field value
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
table (AsyncTable): table instance
|
|
75
|
+
|
|
76
|
+
Keyword Args:
|
|
77
|
+
**kwargs: Arbitrary field values matching the table schema.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
AsyncTableRow: created table row instance
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
84
|
+
>>> from geobox.aio.table import AsyncTable, AsyncTableRow
|
|
85
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
86
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
87
|
+
or
|
|
88
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
89
|
+
>>> row_data = {
|
|
90
|
+
... 'field1': 'value1'
|
|
91
|
+
... }
|
|
92
|
+
>>> row = await table.create_row(row_data)
|
|
93
|
+
or
|
|
94
|
+
>>> row = await AsyncTableRow.create_row(table, row_data)
|
|
95
|
+
"""
|
|
96
|
+
endpoint = urljoin(table.endpoint, 'rows/')
|
|
97
|
+
return await cls._create(table.api, endpoint, kwargs, factory_func=lambda api, item: AsyncTableRow(table, data=item))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
async def get_row(cls,
|
|
102
|
+
table: 'AsyncTable',
|
|
103
|
+
row_id: int,
|
|
104
|
+
user_id: Optional[int],
|
|
105
|
+
) -> 'AsyncTableRow':
|
|
106
|
+
"""
|
|
107
|
+
[async] Get a row by its id
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
table (AsyncTable): the table instance
|
|
111
|
+
row_id (int): the row id
|
|
112
|
+
user_id (int, optional): specific user. privileges required.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
TanbleRow: the table row instance
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
119
|
+
>>> from geobox.aio.table import AsyncTable, AsyncTableRow
|
|
120
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
121
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
122
|
+
or
|
|
123
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
124
|
+
|
|
125
|
+
>>> row = await table.get_row(row_id=1)
|
|
126
|
+
or
|
|
127
|
+
>>> row = await AsyncTableRow.get_row(table, row_id=1)
|
|
128
|
+
"""
|
|
129
|
+
param = {
|
|
130
|
+
'f': 'json',
|
|
131
|
+
'user_id': user_id
|
|
132
|
+
}
|
|
133
|
+
endpoint = urljoin(table.endpoint, f'rows/')
|
|
134
|
+
return await cls._get_detail(table.api, endpoint, uuid=row_id, params=param, factory_func=lambda api, item: AsyncTableRow(table, data=item))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def update(self, **kwargs) -> Dict:
|
|
138
|
+
"""
|
|
139
|
+
[async] Update a row
|
|
140
|
+
|
|
141
|
+
Keyword Args:
|
|
142
|
+
fields to update
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dict: updated row data
|
|
146
|
+
|
|
147
|
+
Example:
|
|
148
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
149
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
150
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
151
|
+
>>> row = await table.get_row(row_id=1)
|
|
152
|
+
>>> await row.update(field1='new_value')
|
|
153
|
+
"""
|
|
154
|
+
await super()._update(self.endpoint, self.data, clean=False)
|
|
155
|
+
return self.data
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def delete(self) -> None:
|
|
159
|
+
"""
|
|
160
|
+
[async] Delete a row
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
None
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
167
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
168
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
169
|
+
>>> row = await table.get_row(row_id=1)
|
|
170
|
+
>>> await row.delete()
|
|
171
|
+
"""
|
|
172
|
+
await super()._delete(self.endpoint)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def _get_other_side_of_relationship(
|
|
176
|
+
self,
|
|
177
|
+
relationship: 'AsyncRelationship',
|
|
178
|
+
) -> Union['AsyncTable', 'AsyncVectorLayer']:
|
|
179
|
+
"""
|
|
180
|
+
[async] Determine which side of a relationship this table is on and return the opposite side.
|
|
181
|
+
|
|
182
|
+
Used internally to navigate bidirectional relationships.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
relationship (AsyncRelationship): The relationship to examine.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
AsyncTable | AsyncVectorLayer: The endpoint (table or layer) on the opposite side
|
|
189
|
+
of the relationship from this table.
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
ValueError: If this table is not part of the given relationship.
|
|
193
|
+
|
|
194
|
+
Note:
|
|
195
|
+
This method assumes the table is either the source or target,
|
|
196
|
+
not the relation table in Many-to-Many relationships.
|
|
197
|
+
"""
|
|
198
|
+
if relationship.source_id == self.table.id:
|
|
199
|
+
return await relationship.get_target()
|
|
200
|
+
|
|
201
|
+
if relationship.target_id == self.table.id:
|
|
202
|
+
return await relationship.get_source()
|
|
203
|
+
|
|
204
|
+
raise ValueError("Relationship does not involve this table.")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def _fetch_related_from_target(
|
|
208
|
+
self,
|
|
209
|
+
target: Union['AsyncTable', 'AsyncVectorLayer'],
|
|
210
|
+
relationship_uuid: str,
|
|
211
|
+
) -> Union[List['AsyncTableRow'], List['AsyncFeature']]:
|
|
212
|
+
"""
|
|
213
|
+
[async] Fetch related rows/features from a relationship target.
|
|
214
|
+
|
|
215
|
+
Internal helper that dispatches to the appropriate API method
|
|
216
|
+
based on target type.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
target (AsyncTable | AsyncVectorLayer): The target endpoint (Table or VectorLayer) to query.
|
|
220
|
+
relationship_uuid (str): UUID of the relationship to traverse.
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
TypeError: If target is not a Table or VectorLayer.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
List[AsyncTableRow] | List[AsyncFeature]: Related rows or features.
|
|
227
|
+
"""
|
|
228
|
+
from .table import AsyncTable
|
|
229
|
+
from .vectorlayer import AsyncVectorLayer
|
|
230
|
+
|
|
231
|
+
if isinstance(target, AsyncTable):
|
|
232
|
+
return await target.get_rows(
|
|
233
|
+
relationship_uuid=relationship_uuid,
|
|
234
|
+
related_record_id=self.id,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if isinstance(target, AsyncVectorLayer):
|
|
238
|
+
return await target.get_features(
|
|
239
|
+
relationship_uuid=relationship_uuid,
|
|
240
|
+
related_record_id=self.id,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
raise TypeError(f"Unsupported target type: {type(target)}")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
async def get_related_records(self,
|
|
247
|
+
relationship_uuid: str,
|
|
248
|
+
) -> Union[List['AsyncTableRow'], List['AsyncFeature']]:
|
|
249
|
+
"""
|
|
250
|
+
[async] Get the related records on the *other side* of the relationship that are linked to this row
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
relationship_uuid (str): The uuid of relationship
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
List[AsyncTableRow] | List[AsyncFeature]: a list of the related records
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
ValueError:
|
|
260
|
+
If the given relationship does not involve the current table
|
|
261
|
+
(i.e., this row is neither the source nor the target of the relationship).
|
|
262
|
+
|
|
263
|
+
TypeError:
|
|
264
|
+
If the relationship target type is not supported for fetching
|
|
265
|
+
related records.
|
|
266
|
+
|
|
267
|
+
Example:
|
|
268
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
269
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
270
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
271
|
+
>>> row = await table.get_row(row_id=1)
|
|
272
|
+
>>> related_records = await row.get_related_records(relationship_uuid="12345678-1234-5678-1234-567812345678")
|
|
273
|
+
"""
|
|
274
|
+
relationship = await self.api.get_relationship(relationship_uuid)
|
|
275
|
+
|
|
276
|
+
other_side = await self._get_other_side_of_relationship(relationship)
|
|
277
|
+
|
|
278
|
+
return await self._fetch_related_from_target(
|
|
279
|
+
target=other_side,
|
|
280
|
+
relationship_uuid=relationship_uuid,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
async def associate_with(
|
|
285
|
+
self,
|
|
286
|
+
relationship_uuid: str,
|
|
287
|
+
*,
|
|
288
|
+
target_ids: Optional[List[int]] = None,
|
|
289
|
+
q: Optional[str] = None,
|
|
290
|
+
) -> Dict:
|
|
291
|
+
"""
|
|
292
|
+
[async] Create relationships between the source record and target records
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
relationship_uuid (str): the relationship uuid
|
|
296
|
+
target_ids (List[int], optional): a list of target record ids to be associated with the current record
|
|
297
|
+
q (str, optional): query filter on target layer or table to select which target features or rows that are going to be related to the current record
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Dict: the record association result
|
|
301
|
+
|
|
302
|
+
Example:
|
|
303
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
304
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
305
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
306
|
+
>>> row = await table.get_row(row_id=1)
|
|
307
|
+
>>> await row.associate_with(
|
|
308
|
+
... relationship_uuid="12345678-1234-5678-1234-567812345678",
|
|
309
|
+
... target_ids=[1, 2, 3],
|
|
310
|
+
... )
|
|
311
|
+
"""
|
|
312
|
+
relationship = await self.api.get_relationship(uuid=relationship_uuid)
|
|
313
|
+
return await relationship.associate_records(
|
|
314
|
+
source_id=self.id,
|
|
315
|
+
target_ids=target_ids,
|
|
316
|
+
q=q,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
async def disassociate_with(
|
|
321
|
+
self,
|
|
322
|
+
relationship_uuid: str,
|
|
323
|
+
*,
|
|
324
|
+
target_ids: Optional[List[int]] = None,
|
|
325
|
+
q: Optional[str] = None,
|
|
326
|
+
) -> Dict:
|
|
327
|
+
"""
|
|
328
|
+
[async] Remove relationships between the source record and target records
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
relationship_uuid (str): the relationship uuid
|
|
332
|
+
target_ids (List[int], optional): a list of target record ids to be disassociated with the current record
|
|
333
|
+
q (str, optional): query filter on target layer or table to select which target features or rows that are going to be related to the current
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Dict: the record disassociation result
|
|
337
|
+
|
|
338
|
+
Example:
|
|
339
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
340
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
341
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
342
|
+
>>> row = await table.get_row(row_id=1)
|
|
343
|
+
>>> await row.disassociate_with(
|
|
344
|
+
... relationship_uuid="12345678-1234-5678-1234-567812345678",
|
|
345
|
+
... target_ids=[1, 2, 3],
|
|
346
|
+
... )
|
|
347
|
+
"""
|
|
348
|
+
relationship = await self.api.get_relationship(uuid=relationship_uuid)
|
|
349
|
+
return await relationship.disassociate_records(
|
|
350
|
+
source_id=self.id,
|
|
351
|
+
target_ids=target_ids,
|
|
352
|
+
q=q,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class AsyncTableField(AsyncBase):
|
|
357
|
+
|
|
358
|
+
def __init__(self,
|
|
359
|
+
table: 'AsyncTable',
|
|
360
|
+
data_type: 'FieldType',
|
|
361
|
+
field_id: int = None,
|
|
362
|
+
data: Optional[Dict] = {},
|
|
363
|
+
):
|
|
364
|
+
"""
|
|
365
|
+
Constructs all the necessary attributes for the Field object.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
table (AsyncTable): The table that the field belongs to.
|
|
369
|
+
data_type (FieldType): type of the field
|
|
370
|
+
field_id (int): the id of the field
|
|
371
|
+
data (Dict, optional): The data of the field.
|
|
372
|
+
"""
|
|
373
|
+
super().__init__(api=table.api, data=data)
|
|
374
|
+
self.table = table
|
|
375
|
+
self.field_id = field_id
|
|
376
|
+
if not isinstance(data_type, FieldType):
|
|
377
|
+
raise ValueError("data_type must be a FieldType instance")
|
|
378
|
+
self.data_type = data_type
|
|
379
|
+
self.endpoint = urljoin(table.endpoint, f'fields/{self.id}/') if self.data.get('id') else None
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def __repr__(self) -> str:
|
|
383
|
+
"""
|
|
384
|
+
Return a string representation of the field.
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
str: The string representation of the field.
|
|
388
|
+
"""
|
|
389
|
+
return f"AsyncTableField(id={self.id}, name={self.name}, data_type={self.data_type})"
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def __getattr__(self, name: str) -> Any:
|
|
393
|
+
"""
|
|
394
|
+
Get an attribute from the resource.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
name (str): The name of the attribute
|
|
398
|
+
"""
|
|
399
|
+
if name == 'datatype':
|
|
400
|
+
return FieldType(self.data['datatype'])
|
|
401
|
+
return super().__getattr__(name)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def domain(self) -> Dict:
|
|
406
|
+
"""
|
|
407
|
+
Domain property
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Dict: domain data
|
|
411
|
+
"""
|
|
412
|
+
return self.data.get('domain')
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@domain.setter
|
|
416
|
+
def domain(self, value: Dict) -> None:
|
|
417
|
+
"""
|
|
418
|
+
Domain property setter
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
None
|
|
422
|
+
"""
|
|
423
|
+
self.data['domain'] = value
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@classmethod
|
|
427
|
+
async def create_field(cls,
|
|
428
|
+
table: 'AsyncTable',
|
|
429
|
+
name: str,
|
|
430
|
+
data_type: 'FieldType',
|
|
431
|
+
data: Dict = {},
|
|
432
|
+
) -> 'AsyncTableField':
|
|
433
|
+
"""
|
|
434
|
+
[async] Create a new field
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
table (AsyncTable): field's table
|
|
438
|
+
name (str): name of the field
|
|
439
|
+
data_type (FieldType): type of the field
|
|
440
|
+
data (Dict, optional): the data of the field
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
AsyncTableField: the created field object
|
|
444
|
+
|
|
445
|
+
Example:
|
|
446
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
447
|
+
>>> from geobox.aio.table import AsyncTable, AsyncTableField
|
|
448
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
449
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
450
|
+
|
|
451
|
+
>>> field = await table.create_field(name='test', data_type=FieldType.Integer)
|
|
452
|
+
or
|
|
453
|
+
>>> field = await AsyncTableField.create_field(client, table=table, name='test', data_type=FieldType.Integer)
|
|
454
|
+
"""
|
|
455
|
+
data.update({
|
|
456
|
+
"name": name,
|
|
457
|
+
"datatype": data_type.value
|
|
458
|
+
})
|
|
459
|
+
endpoint = urljoin(table.endpoint, 'fields/')
|
|
460
|
+
return await super()._create(table.api, endpoint, data, factory_func=lambda api, item: AsyncTableField(table, data_type, item['id'], item))
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
async def delete(self) -> None:
|
|
464
|
+
"""
|
|
465
|
+
[async] Delete the field.
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
None
|
|
469
|
+
|
|
470
|
+
Example:
|
|
471
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
472
|
+
>>> from geobox.field import AsyncTableField
|
|
473
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
474
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
475
|
+
>>> field = await table.get_field(field_id=1)
|
|
476
|
+
>>> await field.delete()
|
|
477
|
+
"""
|
|
478
|
+
await super()._delete(self.endpoint)
|
|
479
|
+
self.field_id = None
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
async def update(self, **kwargs) -> Dict:
|
|
483
|
+
"""
|
|
484
|
+
[async] Update the field.
|
|
485
|
+
|
|
486
|
+
Keyword Args:
|
|
487
|
+
name (str): The name of the field.
|
|
488
|
+
display_name (str): The display name of the field.
|
|
489
|
+
description (str): The description of the field.
|
|
490
|
+
domain (Dict): the domain of the field
|
|
491
|
+
hyperlink (bool): the hyperlink field.
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
Dict: The updated data.
|
|
495
|
+
|
|
496
|
+
Example:
|
|
497
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
498
|
+
>>> from geobox.aio.table import AsyncTableField
|
|
499
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
500
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
501
|
+
>>> field = await table.get_field(field_id=1)
|
|
502
|
+
>>> await field.update(name="my_field", display_name="My Field", description="My Field Description")
|
|
503
|
+
"""
|
|
504
|
+
data = {
|
|
505
|
+
"name": kwargs.get('name'),
|
|
506
|
+
"display_name": kwargs.get('display_name'),
|
|
507
|
+
"description": kwargs.get('description'),
|
|
508
|
+
"domain": kwargs.get('domain'),
|
|
509
|
+
"hyperlink": kwargs.get('hyperlink')
|
|
510
|
+
}
|
|
511
|
+
return await super()._update(self.endpoint, data)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
async def update_domain(self,
|
|
515
|
+
range_domain: Dict = None,
|
|
516
|
+
list_domain: Dict = None,
|
|
517
|
+
) -> Dict:
|
|
518
|
+
"""
|
|
519
|
+
[async] Update field domian values
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
range_domain (Dict): a dictionary with min and max keys.
|
|
523
|
+
list_domain (Dict): a dictionary containing the domain codes and values.
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
Dict: the updated field domain
|
|
527
|
+
|
|
528
|
+
Example:
|
|
529
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
530
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
531
|
+
>>> field = await client.get_table(uuid="12345678-1234-5678-1234-567812345678").get_fields()[0]
|
|
532
|
+
>>> range_d = {'min': 1, 'max': 10}
|
|
533
|
+
>>> await field.update_domain(range_domain = range_d)
|
|
534
|
+
or
|
|
535
|
+
>>> list_d = {'1': 'value1', '2': 'value2'}
|
|
536
|
+
>>> await field.update_domain(list_domain=list_d)
|
|
537
|
+
"""
|
|
538
|
+
if not self.domain:
|
|
539
|
+
self.domain = {'min': None, 'max': None, 'items': {}}
|
|
540
|
+
|
|
541
|
+
if range_domain:
|
|
542
|
+
self.domain['min'] = range_domain['min']
|
|
543
|
+
self.domain['max'] = range_domain['max']
|
|
544
|
+
|
|
545
|
+
if list_domain:
|
|
546
|
+
self.domain['items'] = {**self.domain['items'], **list_domain}
|
|
547
|
+
|
|
548
|
+
await self.update(domain=self.domain)
|
|
549
|
+
return self.domain
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class AsyncRelationship(AsyncBase):
|
|
553
|
+
BASE_ENDPOINT = 'relationships/'
|
|
554
|
+
|
|
555
|
+
def __init__(self,
|
|
556
|
+
api: 'AsyncGeoboxClient',
|
|
557
|
+
uuid: str,
|
|
558
|
+
data: Optional[Dict] = {},
|
|
559
|
+
):
|
|
560
|
+
"""
|
|
561
|
+
Initialize a relationship instance.
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
api (AsyncGeoboxClient): The GeoboxClient instance for making requests.
|
|
565
|
+
uuid (str): The unique identifier for the relationship.
|
|
566
|
+
data (Dict): The response data of the table.
|
|
567
|
+
"""
|
|
568
|
+
super().__init__(api=api, uuid=uuid, data=data)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def __repr__(self) -> str:
|
|
572
|
+
"""
|
|
573
|
+
Return a string representation of the AsyncRelationship.
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
str: The string representation of the AsyncRelationship.
|
|
577
|
+
"""
|
|
578
|
+
return f"AsyncRelationship(uuid={self.uuid}, name={self.relation_name}, cardinality={self.relation_cardinality})"
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
@classmethod
|
|
582
|
+
async def get_relationships(
|
|
583
|
+
cls,
|
|
584
|
+
api: 'AsyncGeoboxClient',
|
|
585
|
+
**kwargs,
|
|
586
|
+
) -> Union[List['AsyncRelationship'], int]:
|
|
587
|
+
"""
|
|
588
|
+
[async] Get a list of relationships with optional filtering and pagination.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
api (AsyncGeoboxClient): The GeoboxClient instance for making requests.
|
|
592
|
+
|
|
593
|
+
Keyword Args:
|
|
594
|
+
q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
|
|
595
|
+
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
|
|
596
|
+
search_fields (str): comma separated list of fields for searching
|
|
597
|
+
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.
|
|
598
|
+
return_count (bool): Whether to return total count. default: False.
|
|
599
|
+
skip (int): Number of items to skip. default: 0
|
|
600
|
+
limit (int): Number of items to return. default: 10
|
|
601
|
+
user_id (int): Specific user. privileges required
|
|
602
|
+
shared (bool): Whether to return shared tables. default: False
|
|
603
|
+
|
|
604
|
+
Returns:
|
|
605
|
+
List[AsyncRelationship] | int: A list of relationship instances or the total number of relationships.
|
|
606
|
+
|
|
607
|
+
Example:
|
|
608
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
609
|
+
>>> from geobox.aio.table import AsyncRelatinship
|
|
610
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
611
|
+
>>> relationships = await client.get_relationships(q="name LIKE '%My relationship%'")
|
|
612
|
+
or
|
|
613
|
+
>>> relationships = await Table.get_relationships(client, q="name LIKE '%My relationship%'")
|
|
614
|
+
"""
|
|
615
|
+
params = {
|
|
616
|
+
'f': 'json',
|
|
617
|
+
'q': kwargs.get('q'),
|
|
618
|
+
'search': kwargs.get('search'),
|
|
619
|
+
'search_fields': kwargs.get('search_fields'),
|
|
620
|
+
'order_by': kwargs.get('order_by'),
|
|
621
|
+
'return_count': kwargs.get('return_count', False),
|
|
622
|
+
'skip': kwargs.get('skip', 0),
|
|
623
|
+
'limit': kwargs.get('limit', 10),
|
|
624
|
+
'user_id': kwargs.get('user_id'),
|
|
625
|
+
'shared': kwargs.get('shared', False),
|
|
626
|
+
}
|
|
627
|
+
return await super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: AsyncRelationship(api, item['uuid'], item))
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
@classmethod
|
|
631
|
+
async def create_relationship(
|
|
632
|
+
cls,
|
|
633
|
+
api: 'AsyncGeoboxClient',
|
|
634
|
+
name: str,
|
|
635
|
+
cardinality: RelationshipCardinality,
|
|
636
|
+
*,
|
|
637
|
+
source: 'RelationshipEndpoint',
|
|
638
|
+
target: 'RelationshipEndpoint',
|
|
639
|
+
relation_table: Optional['AsyncTable'] = None,
|
|
640
|
+
display_name: Optional[str] = None,
|
|
641
|
+
description: Optional[str] = None,
|
|
642
|
+
user_id: Optional[int] = None,
|
|
643
|
+
) -> 'AsyncRelationship':
|
|
644
|
+
"""
|
|
645
|
+
[async] Create a new AsyncRelationship
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
api (AsyncGeoboxClient): The GeoboxClient instance for making requests.
|
|
649
|
+
name (str): name of the relationship
|
|
650
|
+
cardinality (RelationshipCardinality): One to One, One to Many, or Many to Many
|
|
651
|
+
|
|
652
|
+
Keyword Args:
|
|
653
|
+
source (RelationshipEndpoint): Definition of the source side of the relationship, including the table (or layer), field, and foreign-key field
|
|
654
|
+
target (RelationshipEndpoint): Definition of the target side of the relationship, including the table (or layer), field, and foreign-key field
|
|
655
|
+
relation_table (AsyncTable, optional): The table that stores the relationship metadata or join records. (Required for Many-to-Many relationships)
|
|
656
|
+
display_name (str, optional): Human-readable name for the relationship
|
|
657
|
+
description (str, optional): the description of the relationship
|
|
658
|
+
user_id (int, optional): Specific user. privileges required.
|
|
659
|
+
|
|
660
|
+
Returns:
|
|
661
|
+
AsyncRelationship: a relationship instance
|
|
662
|
+
|
|
663
|
+
Example:
|
|
664
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
665
|
+
>>> from geobox.aio.table import AsyncRelationship, RelationshipEndpoint, RelationshipCardinality
|
|
666
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
667
|
+
>>> source = RelationshipEndpoint(
|
|
668
|
+
... table=client.get_table_by_name('owner'),
|
|
669
|
+
... field="name", # on source table
|
|
670
|
+
... fk_field="book_name", # on relation table
|
|
671
|
+
... )
|
|
672
|
+
>>> target = RelationshipEndpoint(
|
|
673
|
+
... table=client.get_table_by_name('parcel'),
|
|
674
|
+
... field="name", # on target table
|
|
675
|
+
... fk_field="author_name", # on relation table
|
|
676
|
+
... )
|
|
677
|
+
>>> relationship = await client.create_relationship(
|
|
678
|
+
... name="book_author",
|
|
679
|
+
... cardinality=RelationshipCardinality.ManytoMany,
|
|
680
|
+
... source=source,
|
|
681
|
+
... target=target,
|
|
682
|
+
... relation_table=client.get_table_by_name('owner_parcel'),
|
|
683
|
+
... )
|
|
684
|
+
or
|
|
685
|
+
>>> relationship = await AsyncRelationship.create_relationship(
|
|
686
|
+
... client,
|
|
687
|
+
... name="owner_parcel",
|
|
688
|
+
... cardinality=RelationshipCardinality.ManytoMany,
|
|
689
|
+
... source=source,
|
|
690
|
+
... target=target,
|
|
691
|
+
... relation_table=client.get_table_by_name('owner_parcel'),
|
|
692
|
+
... )
|
|
693
|
+
"""
|
|
694
|
+
data = {
|
|
695
|
+
"relation_name": name,
|
|
696
|
+
"display_name": display_name,
|
|
697
|
+
"description": description,
|
|
698
|
+
"relation_cardinality": cardinality.value,
|
|
699
|
+
"relation_table_id": relation_table.id if relation_table else None,
|
|
700
|
+
"relation_table_search": f"{relation_table.__class__.__name__.split('Async')[-1]}/{relation_table.name}" if relation_table else None,
|
|
701
|
+
"source_field_name": source.field if type(source.field) == str else source.field.name,
|
|
702
|
+
"source_fk_name": (source.fk_field if not source.fk_field or type(source.fk_field) == str else source.fk_field.name) if relation_table else None,
|
|
703
|
+
"source_id": source.table.id,
|
|
704
|
+
"source_search": f"{source.table.__class__.__name__.split('Async')[-1]}/{source.table.name}",
|
|
705
|
+
"source_type": str(source.table.__class__.__name__.split('Async')[-1]),
|
|
706
|
+
"target_field_name": target.field if type(target.field) == str else target.field.name,
|
|
707
|
+
"target_fk_name": (target.fk_field if not target.fk_field or type(target.fk_field) == str else target.fk_field.name) if relation_table else None,
|
|
708
|
+
"target_id": target.table.id,
|
|
709
|
+
"target_search": f"{target.table.__class__.__name__.split('Async')[-1]}/{target.table.name}",
|
|
710
|
+
"target_type": str(target.table.__class__.__name__.split('Async')[-1]),
|
|
711
|
+
"user_id": user_id,
|
|
712
|
+
}
|
|
713
|
+
return await super()._create(api, cls.BASE_ENDPOINT, data, factory_func=lambda api, item: AsyncRelationship(api, item['uuid'], item))
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@classmethod
|
|
717
|
+
async def get_relationship(
|
|
718
|
+
cls,
|
|
719
|
+
api: 'AsyncGeoboxClient',
|
|
720
|
+
uuid: str,
|
|
721
|
+
user_id: Optional[int] = None,
|
|
722
|
+
) -> 'AsyncRelationship':
|
|
723
|
+
"""
|
|
724
|
+
[async] Get a relationship by UUID.
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
|
|
728
|
+
uuid (str): The UUID of the relationship to get.
|
|
729
|
+
user_id (int, optional): Specific user. privileges required.
|
|
730
|
+
|
|
731
|
+
Returns:
|
|
732
|
+
AsyncRelationship: The AsyncRelationship object.
|
|
733
|
+
|
|
734
|
+
Raises:
|
|
735
|
+
NotFoundError: If the AsyncRelationship with the specified UUID is not found.
|
|
736
|
+
|
|
737
|
+
Example:
|
|
738
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
739
|
+
>>> from geobox.aio.table import AsyncRelationship
|
|
740
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
741
|
+
>>> relationship = await client.get_relationship(uuid="12345678-1234-5678-1234-567812345678")
|
|
742
|
+
or
|
|
743
|
+
>>> relationship = await AsyncRelationship.get_relationship(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
744
|
+
"""
|
|
745
|
+
params = {
|
|
746
|
+
'f': 'json',
|
|
747
|
+
'user_id': user_id,
|
|
748
|
+
}
|
|
749
|
+
return await super()._get_detail(api, cls.BASE_ENDPOINT, uuid, params, factory_func=lambda api, item: AsyncRelationship(api, item['uuid'], item))
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
@classmethod
|
|
753
|
+
async def get_relationship_by_name(
|
|
754
|
+
cls,
|
|
755
|
+
api: 'AsyncGeoboxClient',
|
|
756
|
+
name: str,
|
|
757
|
+
user_id: Optional[int] = None,
|
|
758
|
+
) -> Union['AsyncRelationship', None]:
|
|
759
|
+
"""
|
|
760
|
+
[async] Get a relationship by name
|
|
761
|
+
|
|
762
|
+
Args:
|
|
763
|
+
api (AsyncGeoboxClient): The GeoboxClient instance for making requests.
|
|
764
|
+
name (str): the name of the relationship to get
|
|
765
|
+
user_id (int, optional): specific user. privileges required.
|
|
766
|
+
|
|
767
|
+
Returns:
|
|
768
|
+
AsyncRelationship | None: returns the relationship if a relationship matches the given name, else None
|
|
769
|
+
|
|
770
|
+
Example:
|
|
771
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
772
|
+
>>> from geobox.aio.table import AsyncRelationship
|
|
773
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
774
|
+
>>> relationship = await client.get_relationship_by_name(name='test')
|
|
775
|
+
or
|
|
776
|
+
>>> relationship = await AsyncRelationship.get_relationship_by_name(client, name='test')
|
|
777
|
+
"""
|
|
778
|
+
relationships = await cls.get_relationships(api, q=f"name = '{name}'", user_id=user_id)
|
|
779
|
+
if relationships and relationships[0].relation_name == name:
|
|
780
|
+
return relationships[0]
|
|
781
|
+
else:
|
|
782
|
+
return None
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
async def update(self, **kwargs) -> Dict:
|
|
786
|
+
"""
|
|
787
|
+
[async] Update the relationship.
|
|
788
|
+
|
|
789
|
+
Keyword Args:
|
|
790
|
+
name (str): The name of the relationship.
|
|
791
|
+
display_name (str): The display name of the relationship.
|
|
792
|
+
description (str): The description of the relationship.
|
|
793
|
+
|
|
794
|
+
Returns:
|
|
795
|
+
Dict: The updated relationship data.
|
|
796
|
+
|
|
797
|
+
Raises:
|
|
798
|
+
ValidationError: If the relationship data is invalid.
|
|
799
|
+
|
|
800
|
+
Example:
|
|
801
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
802
|
+
>>> from geobox.aio.table import AsyncRelationship
|
|
803
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
804
|
+
>>> relationship = await AsyncRelationship.get_relationship(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
805
|
+
>>> await relationship.update(display_name="New Display Name")
|
|
806
|
+
"""
|
|
807
|
+
data = {
|
|
808
|
+
"name": kwargs.get('name'),
|
|
809
|
+
"display_name": kwargs.get('display_name'),
|
|
810
|
+
"description": kwargs.get('description'),
|
|
811
|
+
}
|
|
812
|
+
return await super()._update(self.endpoint, data)
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
async def delete(self) -> None:
|
|
816
|
+
"""
|
|
817
|
+
[async] Delete the AsyncRelationship
|
|
818
|
+
|
|
819
|
+
Returns:
|
|
820
|
+
None
|
|
821
|
+
|
|
822
|
+
Example:
|
|
823
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
824
|
+
>>> from geobox.aio.table import AsyncRelationship
|
|
825
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
826
|
+
>>> relationship = await AsyncRelationship.get_relationship(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
827
|
+
>>> await relationship.delete()
|
|
828
|
+
"""
|
|
829
|
+
await super()._delete(self.endpoint)
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
async def get_source(self) -> Union['AsyncTable', 'AsyncVectorLayer']:
|
|
833
|
+
"""
|
|
834
|
+
[async] Get the source table or layer
|
|
835
|
+
|
|
836
|
+
Returns:
|
|
837
|
+
AsyncTable | AsyncVectorLayer: the source table or layer
|
|
838
|
+
|
|
839
|
+
Raises:
|
|
840
|
+
NotFoundError: if the table or layer has been deleted
|
|
841
|
+
|
|
842
|
+
Example:
|
|
843
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
844
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
845
|
+
>>> relationship = await client.get_relationship(uuid="12345678-1234-5678-1234-567812345678")
|
|
846
|
+
>>> source_table = await relationship.get_source()
|
|
847
|
+
"""
|
|
848
|
+
try:
|
|
849
|
+
result = []
|
|
850
|
+
if self.source_type == 'Table':
|
|
851
|
+
result = await self.api.get_tables(
|
|
852
|
+
q=f"id = {self.source_id}"
|
|
853
|
+
)
|
|
854
|
+
elif self.source_type == 'VectorLayer':
|
|
855
|
+
result = await self.api.get_vectors(
|
|
856
|
+
q=f"id = {self.source_id}"
|
|
857
|
+
)
|
|
858
|
+
source = next(source for source in result if source.id == self.source_id)
|
|
859
|
+
return source
|
|
860
|
+
|
|
861
|
+
except StopIteration:
|
|
862
|
+
raise NotFoundError("Table not found!")
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
async def get_target(self) -> Union['AsyncTable', 'AsyncVectorLayer']:
|
|
866
|
+
"""
|
|
867
|
+
[async] Get the target table or layer
|
|
868
|
+
|
|
869
|
+
Returns:
|
|
870
|
+
AsyncTable: the target table or layer
|
|
871
|
+
|
|
872
|
+
Raises:
|
|
873
|
+
NotFoundError: if the table or layer has been deleted
|
|
874
|
+
|
|
875
|
+
Example:
|
|
876
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
877
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
878
|
+
>>> relationship = await client.get_relationship(uuid="12345678-1234-5678-1234-567812345678")
|
|
879
|
+
>>> target_table = await relationship.get_target()
|
|
880
|
+
"""
|
|
881
|
+
try:
|
|
882
|
+
result = []
|
|
883
|
+
if self.target_type == 'Table':
|
|
884
|
+
result = await self.api.get_tables(
|
|
885
|
+
q=f"id = {self.target_id}"
|
|
886
|
+
)
|
|
887
|
+
elif self.target_type == 'VectorLayer':
|
|
888
|
+
result = await self.api.get_vectors(
|
|
889
|
+
q=f"id = {self.target_id}"
|
|
890
|
+
)
|
|
891
|
+
target = next(target for target in result if target.id == self.target_id)
|
|
892
|
+
return target
|
|
893
|
+
|
|
894
|
+
except StopIteration:
|
|
895
|
+
raise NotFoundError("Table not found!")
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
async def get_relation_table(self) -> 'AsyncTable':
|
|
899
|
+
"""
|
|
900
|
+
[async] Get the relation table
|
|
901
|
+
|
|
902
|
+
Returns:
|
|
903
|
+
AsyncTable: the relation table
|
|
904
|
+
|
|
905
|
+
Raises:
|
|
906
|
+
ValueError: If the relationship is not Many-to-Many and thus has no relation table
|
|
907
|
+
NotFoundError: if the table has been deleted
|
|
908
|
+
|
|
909
|
+
Example:
|
|
910
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
911
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
912
|
+
>>> relationship = await client.get_relationship(uuid="12345678-1234-5678-1234-567812345678")
|
|
913
|
+
>>> relation_table = await relationship.get_relation_table()
|
|
914
|
+
"""
|
|
915
|
+
try:
|
|
916
|
+
if not self.relation_table_id:
|
|
917
|
+
raise ValueError(
|
|
918
|
+
"Relationship is not Many-to-Many. "
|
|
919
|
+
"Relation tables are only used for Many-to-Many cardinality."
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
result = await self.api.get_tables(
|
|
923
|
+
q=f"id = {self.relation_table_id}"
|
|
924
|
+
)
|
|
925
|
+
table = next(table for table in result if table.id == self.relation_table_id)
|
|
926
|
+
return table
|
|
927
|
+
|
|
928
|
+
except StopIteration:
|
|
929
|
+
raise NotFoundError("Table not found!")
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
async def associate_records(
|
|
933
|
+
self,
|
|
934
|
+
source_id: int,
|
|
935
|
+
*,
|
|
936
|
+
target_ids: Optional[List[int]] = None,
|
|
937
|
+
q: Optional[str] = None,
|
|
938
|
+
) -> Dict:
|
|
939
|
+
"""
|
|
940
|
+
[async] Create relationships between the source record and target records
|
|
941
|
+
|
|
942
|
+
Args:
|
|
943
|
+
source_id (int): the id of feature/row in the source layer/table
|
|
944
|
+
target_ids (List[int], optional): a list of target record ids to be associated with the current record
|
|
945
|
+
q (str, optional): query filter on target layer or table to select which target features or rows that are going to be related to the current record
|
|
946
|
+
|
|
947
|
+
Returns:
|
|
948
|
+
Dict: the record association result
|
|
949
|
+
|
|
950
|
+
Example:
|
|
951
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
952
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
953
|
+
>>> relationship = await client.get_relationship(uuid="12345678-1234-5678-1234-567812345678")
|
|
954
|
+
>>> result = await relationship.associate_records(
|
|
955
|
+
... source_id=1,
|
|
956
|
+
... target_ids=[1, 2, 3],
|
|
957
|
+
... q="name LIKE '%_school'",
|
|
958
|
+
... )
|
|
959
|
+
"""
|
|
960
|
+
data = {
|
|
961
|
+
'source_id': source_id,
|
|
962
|
+
'target_ids': ', '.join([str(i) for i in target_ids]),
|
|
963
|
+
'q': q,
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
endpoint = f"{self.endpoint}associateRecords/"
|
|
967
|
+
return await self.api.post(
|
|
968
|
+
endpoint,
|
|
969
|
+
clean_data(data),
|
|
970
|
+
is_json=False,
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
async def disassociate_records(
|
|
975
|
+
self,
|
|
976
|
+
source_id: int,
|
|
977
|
+
*,
|
|
978
|
+
target_ids: Optional[List[int]] = None,
|
|
979
|
+
q: Optional[str] = None,
|
|
980
|
+
) -> Dict:
|
|
981
|
+
"""
|
|
982
|
+
[async] Remove relationships between the source record and target records
|
|
983
|
+
|
|
984
|
+
Args:
|
|
985
|
+
source_id (int): the id of feature/row in the source layer/table
|
|
986
|
+
target_ids (List[int], optional): a list of target record ids to be disassociated with the current record
|
|
987
|
+
q (str, optional): query filter on target layer or table to select which target features or rows that are going to be related to the current record
|
|
988
|
+
|
|
989
|
+
Returns:
|
|
990
|
+
Dict: the record disassociation result
|
|
991
|
+
|
|
992
|
+
Example:
|
|
993
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
994
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
995
|
+
>>> relationship = await client.get_relationship(uuid="12345678-1234-5678-1234-567812345678")
|
|
996
|
+
>>> result = await relationship.disassociate_records(
|
|
997
|
+
... source_id=1,
|
|
998
|
+
... target_ids=[1, 2, 3],
|
|
999
|
+
... q="name LIKE '%_school'",
|
|
1000
|
+
... )
|
|
1001
|
+
"""
|
|
1002
|
+
data = {
|
|
1003
|
+
'source_id': source_id,
|
|
1004
|
+
'target_ids': ', '.join([str(i) for i in target_ids]),
|
|
1005
|
+
'q': q,
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
endpoint = f"{self.endpoint}disassociateRecords/"
|
|
1009
|
+
return await self.api.post(
|
|
1010
|
+
endpoint,
|
|
1011
|
+
clean_data(data),
|
|
1012
|
+
is_json=False,
|
|
1013
|
+
)
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
class AsyncTable(AsyncBase):
|
|
1017
|
+
|
|
1018
|
+
BASE_ENDPOINT = 'tables/'
|
|
1019
|
+
|
|
1020
|
+
def __init__(self,
|
|
1021
|
+
api: 'AsyncGeoboxClient',
|
|
1022
|
+
uuid: str,
|
|
1023
|
+
data: Optional[Dict] = {}):
|
|
1024
|
+
"""
|
|
1025
|
+
Initialize a table instance.
|
|
1026
|
+
|
|
1027
|
+
Args:
|
|
1028
|
+
api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
|
|
1029
|
+
uuid (str): The unique identifier for the table.
|
|
1030
|
+
data (Dict): The response data of the table.
|
|
1031
|
+
"""
|
|
1032
|
+
super().__init__(api, uuid=uuid, data=data)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
@classmethod
|
|
1036
|
+
async def get_tables(cls, api: 'AsyncGeoboxClient', **kwargs) -> Union[List['AsyncTable'], int]:
|
|
1037
|
+
"""
|
|
1038
|
+
[async] Get a list of tables with optional filtering and pagination.
|
|
1039
|
+
|
|
1040
|
+
Args:
|
|
1041
|
+
api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
|
|
1042
|
+
|
|
1043
|
+
Keyword Args:
|
|
1044
|
+
include_settings (bool): Whether to include table settings. default: False
|
|
1045
|
+
temporary (bool): Whether to return temporary tables. default: False
|
|
1046
|
+
q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
|
|
1047
|
+
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
|
|
1048
|
+
search_fields (str): comma separated list of fields for searching
|
|
1049
|
+
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.
|
|
1050
|
+
return_count (bool): Whether to return total count. default: False.
|
|
1051
|
+
skip (int): Number of items to skip. default: 0
|
|
1052
|
+
limit (int): Number of items to return. default: 10
|
|
1053
|
+
user_id (int): Specific user. privileges required
|
|
1054
|
+
shared (bool): Whether to return shared tables. default: False
|
|
1055
|
+
|
|
1056
|
+
Returns:
|
|
1057
|
+
List[AsyncTable] | int: A list of table instances or the total number of tables.
|
|
1058
|
+
|
|
1059
|
+
Example:
|
|
1060
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1061
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1062
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1063
|
+
>>> tables = await client.get_tables(q="name LIKE '%My table%'")
|
|
1064
|
+
or
|
|
1065
|
+
>>> tables = await AsyncTable.get_tables(client, q="name LIKE '%My table%'")
|
|
1066
|
+
"""
|
|
1067
|
+
params = {
|
|
1068
|
+
'f': 'json',
|
|
1069
|
+
'include_settings': kwargs.get('include_settings', False),
|
|
1070
|
+
'temporary': kwargs.get('temporary'),
|
|
1071
|
+
'q': kwargs.get('q'),
|
|
1072
|
+
'search': kwargs.get('search'),
|
|
1073
|
+
'search_fields': kwargs.get('search_fields'),
|
|
1074
|
+
'order_by': kwargs.get('order_by'),
|
|
1075
|
+
'return_count': kwargs.get('return_count', False),
|
|
1076
|
+
'skip': kwargs.get('skip', 0),
|
|
1077
|
+
'limit': kwargs.get('limit', 10),
|
|
1078
|
+
'user_id': kwargs.get('user_id'),
|
|
1079
|
+
'shared': kwargs.get('shared', False)
|
|
1080
|
+
}
|
|
1081
|
+
return await super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: AsyncTable(api, item['uuid'], item))
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
@classmethod
|
|
1085
|
+
async def create_table(cls,
|
|
1086
|
+
api: 'AsyncGeoboxClient',
|
|
1087
|
+
name: str,
|
|
1088
|
+
display_name: Optional[str] = None,
|
|
1089
|
+
description: Optional[str] = None,
|
|
1090
|
+
temporary: bool = False,
|
|
1091
|
+
fields: Optional[List[Dict]] = None,
|
|
1092
|
+
) -> 'AsyncTable':
|
|
1093
|
+
"""
|
|
1094
|
+
[async] Create a new table.
|
|
1095
|
+
|
|
1096
|
+
Args:
|
|
1097
|
+
api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
|
|
1098
|
+
name (str): The name of the AsyncTable.
|
|
1099
|
+
display_name (str, optional): The display name of the table.
|
|
1100
|
+
description (str, optional): The description of the table.
|
|
1101
|
+
temporary (bool, optional): Whether to create a temporary tables. default: False
|
|
1102
|
+
fields (List[Dict], optional): raw table fields. you can use create_field method for simpler and safer field addition. required dictionary keys: name, datatype
|
|
1103
|
+
|
|
1104
|
+
Returns:
|
|
1105
|
+
AsyncTable: The newly created table instance.
|
|
1106
|
+
|
|
1107
|
+
Raises:
|
|
1108
|
+
ValidationError: If the table data is invalid.
|
|
1109
|
+
|
|
1110
|
+
Example:
|
|
1111
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1112
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1113
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1114
|
+
>>> table = await client.create_table(name="my_table")
|
|
1115
|
+
or
|
|
1116
|
+
>>> table = await AsyncTable.create_table(client, name="my_table")
|
|
1117
|
+
"""
|
|
1118
|
+
data = {
|
|
1119
|
+
"name": name,
|
|
1120
|
+
"display_name": display_name,
|
|
1121
|
+
"description": description,
|
|
1122
|
+
"temporary": temporary,
|
|
1123
|
+
"fields": fields,
|
|
1124
|
+
}
|
|
1125
|
+
return await super()._create(api, cls.BASE_ENDPOINT, data, factory_func=lambda api, item: AsyncTable(api, item['uuid'], item))
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
@classmethod
|
|
1129
|
+
async def get_table(cls, api: 'AsyncGeoboxClient', uuid: str, user_id: int = None) -> 'AsyncTable':
|
|
1130
|
+
"""
|
|
1131
|
+
[async] Get a table by UUID.
|
|
1132
|
+
|
|
1133
|
+
Args:
|
|
1134
|
+
api (AsyncGeoboxClient): The GeoboxClient instance for making requests.
|
|
1135
|
+
uuid (str): The UUID of the table to get.
|
|
1136
|
+
user_id (int): Specific user. privileges required.
|
|
1137
|
+
|
|
1138
|
+
Returns:
|
|
1139
|
+
AsyncTable: The AsyncTable object.
|
|
1140
|
+
|
|
1141
|
+
Raises:
|
|
1142
|
+
NotFoundError: If the table with the specified UUID is not found.
|
|
1143
|
+
|
|
1144
|
+
Example:
|
|
1145
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1146
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1147
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1148
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
1149
|
+
or
|
|
1150
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1151
|
+
"""
|
|
1152
|
+
params = {
|
|
1153
|
+
'f': 'json',
|
|
1154
|
+
'user_id': user_id,
|
|
1155
|
+
}
|
|
1156
|
+
return await super()._get_detail(api, cls.BASE_ENDPOINT, uuid, params, factory_func=lambda api, item: AsyncTable(api, item['uuid'], item))
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
@classmethod
|
|
1160
|
+
async def get_table_by_name(cls, api: 'AsyncGeoboxClient', name: str, user_id: int = None) -> Union['AsyncTable', None]:
|
|
1161
|
+
"""
|
|
1162
|
+
[async] Get a table by name
|
|
1163
|
+
|
|
1164
|
+
Args:
|
|
1165
|
+
api (AsyncGeoboxClient): The GeoboxClient instance for making requests.
|
|
1166
|
+
name (str): the name of the table to get
|
|
1167
|
+
user_id (int, optional): specific user. privileges required.
|
|
1168
|
+
|
|
1169
|
+
Returns:
|
|
1170
|
+
AsyncTable | None: returns the table if a table matches the given name, else None
|
|
1171
|
+
|
|
1172
|
+
Example:
|
|
1173
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1174
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1175
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1176
|
+
>>> table = await client.get_table_by_name(name='test')
|
|
1177
|
+
or
|
|
1178
|
+
>>> table = await AsyncTable.get_table_by_name(client, name='test')
|
|
1179
|
+
"""
|
|
1180
|
+
tables = await cls.get_tables(api, q=f"name = '{name}'", user_id=user_id)
|
|
1181
|
+
if tables and tables[0].name == name:
|
|
1182
|
+
return tables[0]
|
|
1183
|
+
else:
|
|
1184
|
+
return None
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
async def update(self, **kwargs) -> Dict:
|
|
1188
|
+
"""
|
|
1189
|
+
[async] Update the table.
|
|
1190
|
+
|
|
1191
|
+
Keyword Args:
|
|
1192
|
+
name (str): The name of the table.
|
|
1193
|
+
display_name (str): The display name of the table.
|
|
1194
|
+
description (str): The description of the table.
|
|
1195
|
+
|
|
1196
|
+
Returns:
|
|
1197
|
+
Dict: The updated table data.
|
|
1198
|
+
|
|
1199
|
+
Raises:
|
|
1200
|
+
ValidationError: If the table data is invalid.
|
|
1201
|
+
|
|
1202
|
+
Example:
|
|
1203
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1204
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1205
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1206
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1207
|
+
>>> await table.update(display_name="New Display Name")
|
|
1208
|
+
"""
|
|
1209
|
+
data = {
|
|
1210
|
+
"name": kwargs.get('name'),
|
|
1211
|
+
"display_name": kwargs.get('display_name'),
|
|
1212
|
+
"description": kwargs.get('description'),
|
|
1213
|
+
}
|
|
1214
|
+
return await super()._update(self.endpoint, data)
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
async def delete(self) -> None:
|
|
1218
|
+
"""
|
|
1219
|
+
[async] Delete the AsyncTable.
|
|
1220
|
+
|
|
1221
|
+
Returns:
|
|
1222
|
+
None
|
|
1223
|
+
|
|
1224
|
+
Example:
|
|
1225
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1226
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1227
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1228
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1229
|
+
>>> await table.delete()
|
|
1230
|
+
"""
|
|
1231
|
+
await super()._delete(self.endpoint)
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
@property
|
|
1235
|
+
async def settings(self) -> Dict:
|
|
1236
|
+
"""
|
|
1237
|
+
[async] Get the table's settings.
|
|
1238
|
+
|
|
1239
|
+
Returns:
|
|
1240
|
+
Dict: The table settings.
|
|
1241
|
+
|
|
1242
|
+
Example:
|
|
1243
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1244
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1245
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1246
|
+
>>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1247
|
+
>>> setting = await table.settings
|
|
1248
|
+
"""
|
|
1249
|
+
return await super()._get_settings(endpoint=self.endpoint)
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
async def update_settings(self, settings: Dict) -> Dict:
|
|
1253
|
+
"""
|
|
1254
|
+
[async] Update the settings
|
|
1255
|
+
|
|
1256
|
+
settings (Dict): settings dictionary
|
|
1257
|
+
|
|
1258
|
+
Returns:
|
|
1259
|
+
Dict: updated settings
|
|
1260
|
+
|
|
1261
|
+
Example:
|
|
1262
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1263
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1264
|
+
>>> table1 = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
1265
|
+
>>> table2 = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
1266
|
+
>>> await table1.update_settings(table2.settings)
|
|
1267
|
+
"""
|
|
1268
|
+
return await super()._set_settings(self.endpoint, settings)
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
async def get_fields(self) -> List['AsyncTableField']:
|
|
1272
|
+
"""
|
|
1273
|
+
[async] Get all fields of the table.
|
|
1274
|
+
|
|
1275
|
+
Returns:
|
|
1276
|
+
List[AsyncTableField]: A list of Field instances representing the table's fields.
|
|
1277
|
+
|
|
1278
|
+
Example:
|
|
1279
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1280
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1281
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1282
|
+
>>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1283
|
+
>>> fields = await table.get_fields()
|
|
1284
|
+
"""
|
|
1285
|
+
endpoint = urljoin(self.endpoint, 'fields/')
|
|
1286
|
+
return await super()._get_list(
|
|
1287
|
+
api=self.api,
|
|
1288
|
+
endpoint=endpoint,
|
|
1289
|
+
factory_func=lambda api, item: AsyncTableField(table=self, data_type=FieldType(item['datatype']), field_id=item['id'], data=item),
|
|
1290
|
+
)
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
async def get_field(self, field_id: int) -> 'AsyncTableField':
|
|
1294
|
+
"""
|
|
1295
|
+
[async] Get a specific field by ID.
|
|
1296
|
+
|
|
1297
|
+
Args:
|
|
1298
|
+
field_id (int, optional): The ID of the field to retrieve.
|
|
1299
|
+
|
|
1300
|
+
Returns:
|
|
1301
|
+
AsyncTableField: The requested field instance.
|
|
1302
|
+
|
|
1303
|
+
Raises:
|
|
1304
|
+
NotFoundError: If the field with the specified ID is not found.
|
|
1305
|
+
|
|
1306
|
+
Example:
|
|
1307
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1308
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1309
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1310
|
+
>>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1311
|
+
>>> field = await table.get_field(field_id=1)
|
|
1312
|
+
"""
|
|
1313
|
+
field = next((f for f in await self.get_fields() if f.id == field_id), None)
|
|
1314
|
+
if not field:
|
|
1315
|
+
raise NotFoundError(f'Field with ID {field_id} not found in table {self.name}')
|
|
1316
|
+
|
|
1317
|
+
return field
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
async def get_field_by_name(self, name: str) -> 'AsyncTableField':
|
|
1321
|
+
"""
|
|
1322
|
+
[async] Get a specific field by name.
|
|
1323
|
+
|
|
1324
|
+
Args:
|
|
1325
|
+
name (str): The name of the field to retrieve.
|
|
1326
|
+
|
|
1327
|
+
Returns:
|
|
1328
|
+
AsyncTableField: The requested field instance.
|
|
1329
|
+
|
|
1330
|
+
Raises:
|
|
1331
|
+
NotFoundError: If the field with the specified name is not found.
|
|
1332
|
+
|
|
1333
|
+
Example:
|
|
1334
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1335
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1336
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1337
|
+
>>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1338
|
+
>>> field = await table.get_field_by_name(name='test')
|
|
1339
|
+
"""
|
|
1340
|
+
field = next((f for f in await self.get_fields() if f.name == name), None)
|
|
1341
|
+
if not field:
|
|
1342
|
+
raise NotFoundError(f"Field with name '{name}' not found in table {self.name}")
|
|
1343
|
+
|
|
1344
|
+
return field
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
async def add_field(self, name: str, data_type: 'FieldType', data: Dict = {}) -> 'AsyncTableField':
|
|
1348
|
+
"""
|
|
1349
|
+
[async] Add a new field to the table.
|
|
1350
|
+
|
|
1351
|
+
Args:
|
|
1352
|
+
name (str): The name of the new field.
|
|
1353
|
+
data_type (FieldType): The data type of the new field.
|
|
1354
|
+
data (Dict, optional): Additional field properties (display_name, description, etc.).
|
|
1355
|
+
|
|
1356
|
+
Returns:
|
|
1357
|
+
AsyncTableField: The newly created field instance.
|
|
1358
|
+
|
|
1359
|
+
Raises:
|
|
1360
|
+
ValidationError: If the field data is invalid.
|
|
1361
|
+
|
|
1362
|
+
Example:
|
|
1363
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1364
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1365
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1366
|
+
>>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1367
|
+
>>> field = await table.add_field(name="new_field", data_type=FieldType.String)
|
|
1368
|
+
"""
|
|
1369
|
+
return await AsyncTableField.create_field(self, name=name, data_type=data_type, data=data)
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
async def calculate_field(self,
|
|
1373
|
+
target_field: str,
|
|
1374
|
+
expression: str,
|
|
1375
|
+
q: Optional[str] = None,
|
|
1376
|
+
search: Optional[str] = None,
|
|
1377
|
+
search_fields: Optional[str] = None,
|
|
1378
|
+
row_ids: Optional[str] = None,
|
|
1379
|
+
run_async: bool = True,
|
|
1380
|
+
user_id: Optional[int] = None,
|
|
1381
|
+
) -> Union['AsyncTask', Dict]:
|
|
1382
|
+
"""
|
|
1383
|
+
[async] Calculate values for a field based on an expression.
|
|
1384
|
+
|
|
1385
|
+
Args:
|
|
1386
|
+
target_field (str): The field to calculate values for.
|
|
1387
|
+
expression (str): The expression to use for calculation.
|
|
1388
|
+
q (str, optional): Query to filter features. default: None.
|
|
1389
|
+
search (str, optional): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value
|
|
1390
|
+
search_fields (str, optional): comma separated list of fields for searching
|
|
1391
|
+
row_ids (str, optional): List of specific row IDs to include. default: None
|
|
1392
|
+
run_async (bool, optional): Whether to run the calculation asynchronously. default: True.
|
|
1393
|
+
user_id (int, optional): Specific user. privileges required.
|
|
1394
|
+
|
|
1395
|
+
Returns:
|
|
1396
|
+
Task | Dict: The task instance of the calculation operation or the api response if the run_async=False.
|
|
1397
|
+
|
|
1398
|
+
Example:
|
|
1399
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1400
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1401
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1402
|
+
>>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1403
|
+
>>> task = await table.calculate_field(target_field="target_field",
|
|
1404
|
+
... expression="expression",
|
|
1405
|
+
... q="name like 'my_layer'",
|
|
1406
|
+
... row_ids=[1, 2, 3],
|
|
1407
|
+
... run_async=True)
|
|
1408
|
+
"""
|
|
1409
|
+
data = clean_data({
|
|
1410
|
+
"target_field": target_field,
|
|
1411
|
+
"expression": expression,
|
|
1412
|
+
"q": q,
|
|
1413
|
+
"search": search,
|
|
1414
|
+
"search_fields": search_fields,
|
|
1415
|
+
"row_ids": row_ids,
|
|
1416
|
+
"run_async": run_async,
|
|
1417
|
+
"user_id": user_id
|
|
1418
|
+
})
|
|
1419
|
+
|
|
1420
|
+
endpoint = urljoin(self.endpoint, 'calculateField/')
|
|
1421
|
+
response = await self.api.post(endpoint, data, is_json=False)
|
|
1422
|
+
if run_async:
|
|
1423
|
+
task = await AsyncTask.get_task(self.api, response.get('task_id'))
|
|
1424
|
+
return task
|
|
1425
|
+
|
|
1426
|
+
return response
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
async def get_rows(self, **kwargs) -> List['AsyncTableRow']:
|
|
1430
|
+
"""
|
|
1431
|
+
[async] Query rows of a table
|
|
1432
|
+
|
|
1433
|
+
Keyword Args:
|
|
1434
|
+
relationship_uuid (str): The uuid of relationship
|
|
1435
|
+
related_record_id (int): This is the id of the feature/row that these rows are related to. This id belongs to the related layer/table not this table
|
|
1436
|
+
q (str): Advanced filtering expression, e.g., 'status = "active" and age > 20'
|
|
1437
|
+
search (str): Search term for keyword-based searching among fields/columns
|
|
1438
|
+
search_fields (str): Comma separated column names to search in
|
|
1439
|
+
row_ids (str): Comma separated list of row ids to filter for
|
|
1440
|
+
fields (str): Comma separated column names to include in results, or [ALL]
|
|
1441
|
+
exclude (str): Comma separated column names to exclude from result
|
|
1442
|
+
order_by (str): Comma separated list for ordering, e.g., 'name A, id D'
|
|
1443
|
+
skip (int): Number of records to skip for pagination. default: 0
|
|
1444
|
+
limit (int): Maximum number of records to return. default: 100
|
|
1445
|
+
return_count (bool): If true, returns only the count of matching rows
|
|
1446
|
+
user_id (int): Specific user. privileges required
|
|
1447
|
+
|
|
1448
|
+
Returns:
|
|
1449
|
+
List[AsyncTableRow]: list of table rows objects
|
|
1450
|
+
|
|
1451
|
+
Example:
|
|
1452
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1453
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1454
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1455
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
1456
|
+
or
|
|
1457
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1458
|
+
|
|
1459
|
+
>>> rows = await table.get_rows()
|
|
1460
|
+
"""
|
|
1461
|
+
params = {
|
|
1462
|
+
'f': 'json',
|
|
1463
|
+
'relationship_uuid': kwargs.get('relationship_uuid'),
|
|
1464
|
+
'related_record_id': kwargs.get('related_record_id'),
|
|
1465
|
+
'q': kwargs.get('q'),
|
|
1466
|
+
'search': kwargs.get('search'),
|
|
1467
|
+
'search_fields': kwargs.get('search_fields'),
|
|
1468
|
+
'row_ids': kwargs.get('row_ids'),
|
|
1469
|
+
'fields': kwargs.get('fields'),
|
|
1470
|
+
'exclude': kwargs.get('exclude'),
|
|
1471
|
+
'order_by': kwargs.get('order_by'),
|
|
1472
|
+
'skip': kwargs.get('skip', 0),
|
|
1473
|
+
'limit': kwargs.get('limit', 100),
|
|
1474
|
+
'return_count': kwargs.get('return_count', False),
|
|
1475
|
+
'user_id': kwargs.get('user_id'),
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
endpoint = f'{self.endpoint}rows/'
|
|
1479
|
+
|
|
1480
|
+
return await super()._get_list(
|
|
1481
|
+
api=self.api,
|
|
1482
|
+
endpoint=endpoint,
|
|
1483
|
+
params=params,
|
|
1484
|
+
factory_func=lambda api, item: AsyncTableRow(self, item),
|
|
1485
|
+
)
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
async def get_row(self,
|
|
1489
|
+
row_id: int,
|
|
1490
|
+
user_id: Optional[int] = None,
|
|
1491
|
+
) -> 'AsyncTableRow':
|
|
1492
|
+
"""
|
|
1493
|
+
[async] Get a row by its id
|
|
1494
|
+
|
|
1495
|
+
Args:
|
|
1496
|
+
row_id (int): the row id
|
|
1497
|
+
user_id (int, optional): specific user. privileges required.
|
|
1498
|
+
|
|
1499
|
+
Returns:
|
|
1500
|
+
TanbleRow: the table row instance
|
|
1501
|
+
|
|
1502
|
+
Example:
|
|
1503
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1504
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1505
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1506
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
1507
|
+
or
|
|
1508
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1509
|
+
|
|
1510
|
+
>>> row = await table.get_row(row_id=1)
|
|
1511
|
+
"""
|
|
1512
|
+
return await AsyncTableRow.get_row(self, row_id, user_id)
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
async def create_row(self, **kwargs) -> 'AsyncTableRow':
|
|
1516
|
+
"""
|
|
1517
|
+
[async] Create a new row in the table.
|
|
1518
|
+
|
|
1519
|
+
Each keyword argument represents a field value for the row, where:
|
|
1520
|
+
- The keyword is the field name
|
|
1521
|
+
- The value is the field value
|
|
1522
|
+
|
|
1523
|
+
Keyword Args:
|
|
1524
|
+
**kwargs: Arbitrary field values matching the table schema.
|
|
1525
|
+
|
|
1526
|
+
Returns:
|
|
1527
|
+
AsyncTableRow: created table row instance
|
|
1528
|
+
|
|
1529
|
+
Example:
|
|
1530
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1531
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1532
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1533
|
+
>>> table = await client.get_table(uuid="12345678-1234-5678-1234-567812345678")
|
|
1534
|
+
or
|
|
1535
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1536
|
+
|
|
1537
|
+
>>> row = await table.create_row(
|
|
1538
|
+
... field1=value1
|
|
1539
|
+
... )
|
|
1540
|
+
"""
|
|
1541
|
+
return await AsyncTableRow.create_row(self, **kwargs)
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
async def import_rows(self,
|
|
1545
|
+
file: 'AsyncFile',
|
|
1546
|
+
*,
|
|
1547
|
+
file_encoding: str = "utf-8",
|
|
1548
|
+
input_dataset: Optional[str] = None,
|
|
1549
|
+
delimiter: str = ',',
|
|
1550
|
+
has_header: bool = True,
|
|
1551
|
+
report_errors: bool = False,
|
|
1552
|
+
bulk_insert: bool = True,
|
|
1553
|
+
) -> 'AsyncTask':
|
|
1554
|
+
"""
|
|
1555
|
+
Import rows from a CSV file into a table
|
|
1556
|
+
|
|
1557
|
+
Args:
|
|
1558
|
+
file (AsyncFile): file object to import.
|
|
1559
|
+
file_encoding (str, optional): Character encoding of the input file. default: utf-8
|
|
1560
|
+
input_dataset (str, optional): Name of the dataset in the input file.
|
|
1561
|
+
delimiter (str, optional): the delimiter of the dataset. default: ,
|
|
1562
|
+
has_header (bool, optional): Whether the file has header or not. default: True
|
|
1563
|
+
report_errors (bool, optional): Whether to report import errors. default: False
|
|
1564
|
+
bulk_insert (bool, optional):
|
|
1565
|
+
|
|
1566
|
+
Returns:
|
|
1567
|
+
AsyncTask: The task instance of the import operation.
|
|
1568
|
+
|
|
1569
|
+
Raises:
|
|
1570
|
+
ValidationError: If the import parameters are invalid.
|
|
1571
|
+
|
|
1572
|
+
Example:
|
|
1573
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1574
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1575
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1576
|
+
>>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1577
|
+
>>> file = await client.get_file(uuid="12345678-1234-5678-1234-567812345678")
|
|
1578
|
+
>>> task = await table.import_rows(
|
|
1579
|
+
... file=file,
|
|
1580
|
+
... )
|
|
1581
|
+
"""
|
|
1582
|
+
data = clean_data({
|
|
1583
|
+
"file_uuid": file.uuid,
|
|
1584
|
+
"file_encoding": file_encoding,
|
|
1585
|
+
"input_dataset": file.name if not input_dataset else input_dataset,
|
|
1586
|
+
"delimiter": delimiter,
|
|
1587
|
+
"has_header": has_header,
|
|
1588
|
+
"report_errors": report_errors,
|
|
1589
|
+
"bulk_insert": bulk_insert,
|
|
1590
|
+
})
|
|
1591
|
+
|
|
1592
|
+
endpoint = urljoin(self.endpoint, 'import-csv/')
|
|
1593
|
+
response = await self.api.post(endpoint, data, is_json=False)
|
|
1594
|
+
task = await AsyncTask.get_task(self.api, response.get('task_id'))
|
|
1595
|
+
return task
|
|
1596
|
+
|
|
1597
|
+
|
|
1598
|
+
async def export_rows(self,
|
|
1599
|
+
out_filename: str,
|
|
1600
|
+
*,
|
|
1601
|
+
out_format: 'TableExportFormat' = TableExportFormat.CSV,
|
|
1602
|
+
q: Optional[str] = None,
|
|
1603
|
+
search: Optional[str] = None,
|
|
1604
|
+
search_fields: Optional[str] = None,
|
|
1605
|
+
row_ids: Optional[str] = None,
|
|
1606
|
+
fields: Optional[str] = None,
|
|
1607
|
+
exclude: Optional[str] = None,
|
|
1608
|
+
order_by: Optional[str] = None,
|
|
1609
|
+
zipped: bool = False,
|
|
1610
|
+
run_async: bool = True,
|
|
1611
|
+
) -> Union['AsyncTask', str]:
|
|
1612
|
+
"""
|
|
1613
|
+
[async] Export rows of a table to a file
|
|
1614
|
+
|
|
1615
|
+
Args:
|
|
1616
|
+
out_filename (str): Name of the output file without the format (.csv)
|
|
1617
|
+
out_format (TableExportFormat, optional): Format of the output file
|
|
1618
|
+
q (str, optional): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
|
|
1619
|
+
search (str, optional): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value
|
|
1620
|
+
search_fields (str, optional): comma separated list of fields for searching
|
|
1621
|
+
row_ids (str, optional): List of specific row IDs to include
|
|
1622
|
+
fields (str, optional): List of specific field names to include
|
|
1623
|
+
exclude (str, optional): List of specific field names to exclude
|
|
1624
|
+
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.
|
|
1625
|
+
zipped (str, optional): Whether to compress the output file
|
|
1626
|
+
run_async (bool, optional): Whether to run the export asynchronously. default: True
|
|
1627
|
+
|
|
1628
|
+
Returns:
|
|
1629
|
+
AsyncTask | Dict: The task instance of the export operation (run_async=True) or the export result (run_async=False)
|
|
1630
|
+
|
|
1631
|
+
Raises:
|
|
1632
|
+
ValidationError: If the export parameters are invalid.
|
|
1633
|
+
|
|
1634
|
+
Example:
|
|
1635
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1636
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1637
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1638
|
+
>>> table = await AsyncTable.get_table(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1639
|
+
>>> file = await client.get_file(uuid="12345678-1234-5678-1234-567812345678")
|
|
1640
|
+
>>> task = await table.export_rows(
|
|
1641
|
+
... file=file,
|
|
1642
|
+
... )
|
|
1643
|
+
"""
|
|
1644
|
+
data = clean_data({
|
|
1645
|
+
"out_filename": out_filename,
|
|
1646
|
+
"out_format": out_format.value if out_format else None,
|
|
1647
|
+
"q": q,
|
|
1648
|
+
"search": search,
|
|
1649
|
+
"search_fields": search_fields,
|
|
1650
|
+
"row_ids": row_ids,
|
|
1651
|
+
"fields": fields,
|
|
1652
|
+
"exclude": exclude,
|
|
1653
|
+
"order_by": order_by,
|
|
1654
|
+
"zipped": zipped,
|
|
1655
|
+
"run_async": run_async,
|
|
1656
|
+
})
|
|
1657
|
+
|
|
1658
|
+
endpoint = urljoin(self.endpoint, 'export/')
|
|
1659
|
+
response = await self.api.post(endpoint, data, is_json=False)
|
|
1660
|
+
if run_async:
|
|
1661
|
+
task = await AsyncTask.get_task(self.api, response.get('task_id'))
|
|
1662
|
+
return task
|
|
1663
|
+
|
|
1664
|
+
return response
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
async def share(self, users: List['AsyncUser']) -> None:
|
|
1668
|
+
"""
|
|
1669
|
+
[async] Shares the table with specified users.
|
|
1670
|
+
|
|
1671
|
+
Args:
|
|
1672
|
+
users (List[AsyncUser]): The list of user objects to share the table with.
|
|
1673
|
+
|
|
1674
|
+
Returns:
|
|
1675
|
+
None
|
|
1676
|
+
|
|
1677
|
+
Example:
|
|
1678
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1679
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1680
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1681
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1682
|
+
>>> users = await client.search_users(search='John')
|
|
1683
|
+
>>> await table.share(users=users)
|
|
1684
|
+
"""
|
|
1685
|
+
await super()._share(self.endpoint, users)
|
|
1686
|
+
|
|
1687
|
+
|
|
1688
|
+
async def unshare(self, users: List['AsyncUser']) -> None:
|
|
1689
|
+
"""
|
|
1690
|
+
[async] Unshares the table with specified users.
|
|
1691
|
+
|
|
1692
|
+
Args:
|
|
1693
|
+
users (List[User]): The list of user objects to unshare the table with.
|
|
1694
|
+
|
|
1695
|
+
Returns:
|
|
1696
|
+
None
|
|
1697
|
+
|
|
1698
|
+
Example:
|
|
1699
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1700
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1701
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1702
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1703
|
+
>>> users = await client.search_users(search='John')
|
|
1704
|
+
>>> await table.unshare(users=users)
|
|
1705
|
+
"""
|
|
1706
|
+
await super()._unshare(self.endpoint, users)
|
|
1707
|
+
|
|
1708
|
+
|
|
1709
|
+
async def get_shared_users(self, search: str = None, skip: int = 0, limit: int = 10) -> List['AsyncUser']:
|
|
1710
|
+
"""
|
|
1711
|
+
[async] Retrieves the list of users the table is shared with.
|
|
1712
|
+
|
|
1713
|
+
Args:
|
|
1714
|
+
search (str, optional): The search query.
|
|
1715
|
+
skip (int, optional): The number of users to skip.
|
|
1716
|
+
limit (int, optional): The maximum number of users to retrieve.
|
|
1717
|
+
|
|
1718
|
+
Returns:
|
|
1719
|
+
List[AsyncUser]: The list of shared users.
|
|
1720
|
+
|
|
1721
|
+
Example:
|
|
1722
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
1723
|
+
>>> from geobox.aio.table import AsyncTable
|
|
1724
|
+
>>> async with AsyncGeoboxClient() as client:
|
|
1725
|
+
>>> table = await AsyncTable.get_table(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
1726
|
+
>>> await table.get_shared_users(search='John', skip=0, limit=10)
|
|
1727
|
+
"""
|
|
1728
|
+
params = {
|
|
1729
|
+
'search': search,
|
|
1730
|
+
'skip': skip,
|
|
1731
|
+
'limit': limit
|
|
1732
|
+
}
|
|
1733
|
+
return await super()._get_shared_users(self.endpoint, params)
|