geobox 1.4.1__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. geobox/__init__.py +2 -2
  2. geobox/aio/__init__.py +63 -0
  3. geobox/aio/api.py +2640 -0
  4. geobox/aio/apikey.py +263 -0
  5. geobox/aio/attachment.py +339 -0
  6. geobox/aio/base.py +262 -0
  7. geobox/aio/basemap.py +196 -0
  8. geobox/aio/dashboard.py +342 -0
  9. geobox/aio/feature.py +527 -0
  10. geobox/aio/field.py +321 -0
  11. geobox/aio/file.py +522 -0
  12. geobox/aio/layout.py +341 -0
  13. geobox/aio/log.py +145 -0
  14. geobox/aio/map.py +1034 -0
  15. geobox/aio/model3d.py +415 -0
  16. geobox/aio/mosaic.py +696 -0
  17. geobox/aio/plan.py +315 -0
  18. geobox/aio/query.py +702 -0
  19. geobox/aio/raster.py +869 -0
  20. geobox/aio/route.py +63 -0
  21. geobox/aio/scene.py +342 -0
  22. geobox/aio/settings.py +194 -0
  23. geobox/aio/task.py +402 -0
  24. geobox/aio/tile3d.py +339 -0
  25. geobox/aio/tileset.py +672 -0
  26. geobox/aio/usage.py +243 -0
  27. geobox/aio/user.py +507 -0
  28. geobox/aio/vectorlayer.py +1363 -0
  29. geobox/aio/version.py +273 -0
  30. geobox/aio/view.py +983 -0
  31. geobox/aio/workflow.py +341 -0
  32. geobox/api.py +14 -13
  33. geobox/apikey.py +28 -1
  34. geobox/attachment.py +27 -1
  35. geobox/base.py +4 -4
  36. geobox/basemap.py +30 -1
  37. geobox/dashboard.py +27 -0
  38. geobox/feature.py +33 -13
  39. geobox/field.py +33 -21
  40. geobox/file.py +40 -46
  41. geobox/layout.py +28 -1
  42. geobox/log.py +31 -7
  43. geobox/map.py +56 -5
  44. geobox/model3d.py +98 -19
  45. geobox/mosaic.py +47 -7
  46. geobox/plan.py +29 -3
  47. geobox/query.py +41 -5
  48. geobox/raster.py +45 -13
  49. geobox/scene.py +26 -0
  50. geobox/settings.py +30 -1
  51. geobox/task.py +28 -6
  52. geobox/tile3d.py +27 -1
  53. geobox/tileset.py +26 -5
  54. geobox/usage.py +32 -1
  55. geobox/user.py +62 -6
  56. geobox/utils.py +34 -0
  57. geobox/vectorlayer.py +59 -4
  58. geobox/version.py +25 -1
  59. geobox/view.py +54 -15
  60. geobox/workflow.py +27 -1
  61. {geobox-1.4.1.dist-info → geobox-2.0.0.dist-info}/METADATA +4 -1
  62. geobox-2.0.0.dist-info/RECORD +68 -0
  63. geobox-1.4.1.dist-info/RECORD +0 -38
  64. {geobox-1.4.1.dist-info → geobox-2.0.0.dist-info}/WHEEL +0 -0
  65. {geobox-1.4.1.dist-info → geobox-2.0.0.dist-info}/licenses/LICENSE +0 -0
  66. {geobox-1.4.1.dist-info → geobox-2.0.0.dist-info}/top_level.txt +0 -0
geobox/aio/apikey.py ADDED
@@ -0,0 +1,263 @@
1
+ from typing import List, Dict, Optional, TYPE_CHECKING
2
+ from urllib.parse import urljoin
3
+
4
+ from .base import AsyncBase
5
+ from ..utils import clean_data
6
+
7
+ if TYPE_CHECKING:
8
+ from . import AsyncGeoboxClient
9
+ from ..api import GeoboxClient as SyncGeoboxClient
10
+ from ..apikey import ApiKey as SyncApikey
11
+
12
+
13
+ class ApiKey(AsyncBase):
14
+
15
+ BASE_ENDPOINT = 'apikeys/'
16
+
17
+ def __init__(self,
18
+ api: 'AsyncGeoboxClient',
19
+ key_id: int,
20
+ data: Optional[Dict] = {}):
21
+ """
22
+ Initialize an apikey instance.
23
+
24
+ Args:
25
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
26
+ key_id (int): The unique identifier for the apikey.
27
+ data (Dict, optional): The data of the apikey.
28
+ """
29
+ super().__init__(api, data=data)
30
+ self.key_id = key_id
31
+ self.endpoint = urljoin(self.BASE_ENDPOINT, str(self.id))
32
+
33
+
34
+ def __repr__(self) -> str:
35
+ """
36
+ Return a string representation of the attachment.
37
+
38
+ Returns:
39
+ str: The string representation of the attachment.
40
+ """
41
+ return f'ApiKey(id={self.key_id}, name={self.name}, revoked={self.revoked})'
42
+
43
+
44
+ @classmethod
45
+ async def get_apikeys(cls, api: 'AsyncGeoboxClient', **kwargs) -> List['ApiKey']:
46
+ """
47
+ [async] Get a list of apikeys
48
+
49
+ Args:
50
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
51
+
52
+ Keyword Args:
53
+ search (str): search term for keyword-based searching among all textual fields.
54
+ 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.
55
+ skip (int): Number of layers to skip. default is 0.
56
+ limit (int): Maximum number of layers to return. default is 10.
57
+ user_id (int): Specific user. privileges required.
58
+
59
+ Example:
60
+ >>> from geobox.aio import AsyncGeoboxClient
61
+ >>> from geobox.aio.apikey import ApiKey
62
+ >>> async with AsyncGeoboxClient() as client:
63
+ >>> apikeys = await ApiKey.get_apikeys(client)
64
+ or
65
+ >>> apikeys = await client.get_apikeys()
66
+ """
67
+ params = {
68
+ 'search': kwargs.get('search'),
69
+ 'order_by': kwargs.get('order_by'),
70
+ 'skip': kwargs.get('skip'),
71
+ 'limit': kwargs.get('limit'),
72
+ 'user_id': kwargs.get('user_id')
73
+ }
74
+ return await super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: ApiKey(api, item['id'], item))
75
+
76
+
77
+ @classmethod
78
+ async def create_apikey(cls, api: 'AsyncGeoboxClient', name: str, user_id: int = None) -> 'ApiKey':
79
+ """
80
+ [async] Create an ApiKey
81
+
82
+ Args:
83
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
84
+ name (str): name of the key.
85
+ user_id (int, optional): Specific user. privileges required.
86
+
87
+ Returns:
88
+ ApiKey: the apikey object
89
+
90
+ Example:
91
+ >>> from geobox.aio import AsyncGeoboxClient
92
+ >>> from geobox.aio.apikey import ApiKey
93
+ >>> async with AsyncGeoboxClient() as client:
94
+ >>> apikey = await ApiKey.create_apikey(client, name='test')
95
+ or
96
+ >>> apikey = await client.create_apikey(name='test')
97
+ """
98
+ data = clean_data({
99
+ 'name': name,
100
+ 'user_id': user_id
101
+ })
102
+ response = await api.post(cls.BASE_ENDPOINT, payload=data, is_json=False)
103
+ return ApiKey(api, response['id'], response)
104
+
105
+
106
+ @classmethod
107
+ async def get_apikey(cls, api: 'AsyncGeoboxClient', key_id: int) -> 'ApiKey':
108
+ """
109
+ [async] Get an ApiKey
110
+
111
+ Args:
112
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
113
+ key_id (str): the id of the apikey.
114
+
115
+ Returns:
116
+ ApiKey: the ApiKey object
117
+
118
+ Example:
119
+ >>> from geobox.aio import AsyncGeoboxClient
120
+ >>> from geobox.aio.apikey import ApiKey
121
+ >>> async with AsyncGeoboxClient() as client:
122
+ >>> apikey = await ApiKey.get_apikey(client, key_id=1)
123
+ or
124
+ >>> apikey = await client.get_apikey(key_id=1)
125
+ """
126
+ params = {
127
+ 'f': 'json'
128
+ }
129
+ return await super()._get_detail(api=api,
130
+ endpoint=cls.BASE_ENDPOINT,
131
+ uuid=key_id,
132
+ params=params,
133
+ factory_func=lambda api, item: ApiKey(api, item['id'], item))
134
+
135
+
136
+ @classmethod
137
+ async def get_apikey_by_name(cls, api: 'AsyncGeoboxClient', name: str, user_id: int = None) -> 'ApiKey':
138
+ """
139
+ [async] Get an ApiKey by name
140
+
141
+ Args:
142
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
143
+ name (str): the name of the key to get
144
+ user_id (int, optional): specific user. privileges required.
145
+
146
+ Returns:
147
+ ApiKey | None: returns the key if a key matches the given name, else None
148
+
149
+ Example:
150
+ >>> from geobox.aio import AsyncGeoboxClient
151
+ >>> from geobox.aio.apikey import ApiKey
152
+ >>> async with AsyncGeoboxClient() as client:
153
+ >>> apikey = await ApiKey.get_apikey_by_name(client, name='test')
154
+ or
155
+ >>> apikey = await client.get_apikey_by_name(name='test')
156
+ """
157
+ apikeys = await cls.get_apikeys(api, search=name, user_id=user_id)
158
+ if apikeys and apikeys[0].name == name:
159
+ return apikeys[0]
160
+ else:
161
+ return None
162
+
163
+
164
+ async def update(self, name: str, user_id: int = None) -> Dict:
165
+ """
166
+ [async] Update an ApiKey
167
+
168
+ Args:
169
+ name (str): the name of the key
170
+ user_id (int, optional): Specific user. privileges required.
171
+
172
+ Returns:
173
+ Dict: Updated ApiKey data
174
+
175
+ Example:
176
+ >>> from geobox.aio import AsyncGeoboxClient
177
+ >>> from geobox.aio.apikey import ApiKey
178
+ >>> async with AsyncGeoboxClient() as client:
179
+ >>> apikey = await ApiKey.get_apikey(client, key_id=1)
180
+ >>> await apikey.update(name="updated_name")
181
+ """
182
+ data = clean_data({
183
+ "name": name,
184
+ "user_id": user_id
185
+ })
186
+
187
+ response = await self.api.put(self.endpoint, data, is_json=False)
188
+ self._update_properties(response)
189
+ return response
190
+
191
+
192
+ async def delete(self) -> None:
193
+ """
194
+ [async] Delete the ApiKey.
195
+
196
+ Returns:
197
+ None
198
+
199
+ Example:
200
+ >>> from geobox.aio import AsyncGeoboxClient
201
+ >>> from geobox.aio.apikey import ApiKey
202
+ >>> async with AsyncGeoboxClient() as client:
203
+ >>> apikey = await ApiKey.get_apikey(client, key_id=1)
204
+ >>> await apikey.delete()
205
+ """
206
+ await super().delete(self.endpoint)
207
+ self.key_id = None
208
+
209
+
210
+ async def revoke(self) -> None:
211
+ """
212
+ [async] Revoke an ApiKey
213
+
214
+ Example:
215
+ >>> from geobox.aio import AsyncGeoboxClient
216
+ >>> from geobox.aio.apikey import ApiKey
217
+ >>> async with AsyncGeoboxClient() as client:
218
+ >>> apikey = await ApiKey.get_apikey(client, key_id=1)
219
+ >>> await apikey.revoke()
220
+ """
221
+ endpoint = f"{self.endpoint}/revoke"
222
+ await self.api.post(endpoint)
223
+ self.data['revoked'] = True
224
+
225
+
226
+ async def grant(self) -> None:
227
+ """
228
+ [async] Grant an ApiKey
229
+
230
+ Example:
231
+ >>> from geobox.aio import AsyncGeoboxClient
232
+ >>> from geobox.aio.apikey import ApiKey
233
+ >>> async with AsyncGeoboxClient() as client:
234
+ >>> apikey = await ApiKey.get_apikey(client, key_id=1)
235
+ >>> await apikey.grant()
236
+ """
237
+ endpoint = f"{self.endpoint}/grant"
238
+ await self.api.post(endpoint)
239
+ self.data['revoked'] = False
240
+
241
+
242
+ def to_sync(self, sync_client: 'SyncGeoboxClient') -> 'SyncApikey':
243
+ """
244
+ Switch to sync version of the apikey instance to have access to the sync methods
245
+
246
+ Args:
247
+ sync_client (SyncGeoboxClient): The sync version of the GeoboxClient instance for making requests.
248
+
249
+ Returns:
250
+ geobox.apikey.Apikey: the sync instance of the apikey.
251
+
252
+ Example:
253
+ >>> from geobox import Geoboxclient
254
+ >>> from geobox.aio import AsyncGeoboxClient
255
+ >>> from geobox.aio.apikey import ApiKey
256
+ >>> client = GeoboxClient()
257
+ >>> async with AsyncGeoboxClient() as async_client:
258
+ >>> apikey = await ApiKey.get_apikey(async_client, key_id=1)
259
+ >>> sync_apikey = apikey.to_sync(client)
260
+ """
261
+ from ..apikey import ApiKey as SyncApiKey
262
+
263
+ return SyncApiKey(api=sync_client, key_id=self.key_id, data=self.data)
@@ -0,0 +1,339 @@
1
+ from typing import List, Dict, Optional, TYPE_CHECKING, Union
2
+ from urllib.parse import urljoin
3
+
4
+ from .base import AsyncBase
5
+ from .map import Map
6
+ from .vectorlayer import VectorLayer
7
+ from .view import VectorLayerView
8
+ from .file import File
9
+ from ..utils import clean_data
10
+
11
+ if TYPE_CHECKING:
12
+ from . import AsyncGeoboxClient
13
+ from .feature import Feature
14
+ from ..api import GeoboxClient as SyncGeoboxClient
15
+ from ..attachment import Attachment as SyncAtachment
16
+
17
+
18
+ class Attachment(AsyncBase):
19
+
20
+ BASE_ENDPOINT = 'attachments/'
21
+
22
+ def __init__(self,
23
+ api: 'AsyncGeoboxClient',
24
+ attachment_id: str,
25
+ data: Optional[Dict] = {}):
26
+ """
27
+ Initialize an Attachment instance.
28
+
29
+ Args:
30
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
31
+ attachment_id (str): The id for the attachment.
32
+ data (Dict, optional): The data of the attachment.
33
+ """
34
+ super().__init__(api, data=data)
35
+ self.attachment_id = attachment_id
36
+ self.endpoint = f"{self.BASE_ENDPOINT}{str(self.attachment_id)}/"
37
+
38
+
39
+ def __repr__(self) -> str:
40
+ """
41
+ Return a string representation of the attachment.
42
+
43
+ Returns:
44
+ str: The string representation of the attachment.
45
+ """
46
+ return f"Attachment(id={self.attachment_id}, name={self.name})"
47
+
48
+
49
+ @property
50
+ def file(self) -> 'File':
51
+ """
52
+ Attachment file property
53
+
54
+ Returns:
55
+ File: the file object
56
+ """
57
+ return File(self.api, self.data['file'].get('uuid'), self.data['file'])
58
+
59
+
60
+ @classmethod
61
+ async def get_attachments(cls, api: 'AsyncGeoboxClient', resource: Union['Map', 'VectorLayer', 'VectorLayerView'], **kwargs) -> Union[List['Attachment'], int]:
62
+ """
63
+ [async] Get list of attachments with optional filtering and pagination.
64
+
65
+ Args:
66
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
67
+ resource (Map | VectorLayer | VectorLayerView): options are: Map, Vector, View objects
68
+
69
+ Keyword Args:
70
+ element_id (str): the id of the element with attachment.
71
+ search (str): search term for keyword-based searching among all textual fields.
72
+ 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.
73
+ skip (int): Number of items to skip. default is 0.
74
+ limit (int): Number of items to return. default is 10.
75
+ return_count (bool): Whether to return total count. default is False.
76
+
77
+ Returns:
78
+ List[Attachment] | int: A list of attachments instances or the total number of attachments.
79
+
80
+ Raises:
81
+ TypeError: if the resource type is not supported
82
+
83
+ Example:
84
+ >>> from geobox.aio import AsyncGeoboxClient
85
+ >>> from geobox.aio.attachment import Attachment
86
+ >>> async with AsyncGeoboxClient() as client:
87
+ >>> maps = await client.get_maps()
88
+ >>> map = maps[0]
89
+ >>> attachments = await Attachment.get_attachments(client, resource=map, q="name LIKE '%My attachment%'")
90
+ or
91
+ >>> attachments = await client.get_attachments(resource=map, q="name LIKE '%My attachment%'")
92
+ """
93
+ if type(resource) == VectorLayer:
94
+ resource_type = 'vector'
95
+
96
+ elif type(resource) == VectorLayerView:
97
+ resource_type = 'view'
98
+
99
+ elif type(resource) == Map:
100
+ resource_type = 'map'
101
+
102
+ else:
103
+ raise TypeError('resource must be a vectorlayer or view or map object')
104
+
105
+
106
+ params = {
107
+ 'resource_type': resource_type,
108
+ 'resource_uuid': resource.uuid,
109
+ 'element_id': kwargs.get('element_id'),
110
+ 'search': kwargs.get('search'),
111
+ 'order_by': kwargs.get('order_by'),
112
+ 'skip': kwargs.get('skip', 0),
113
+ 'limit': kwargs.get('limit', 10),
114
+ 'return_count': kwargs.get('return_count')
115
+ }
116
+ return await super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: Attachment(api, item['id'], item))
117
+
118
+
119
+ @classmethod
120
+ async def create_attachment(cls,
121
+ api: 'AsyncGeoboxClient',
122
+ name: str,
123
+ loc_x: int,
124
+ loc_y: int,
125
+ resource: Union['Map', 'VectorLayer', 'VectorLayerView'],
126
+ file: 'File',
127
+ feature: 'Feature' = None,
128
+ display_name: str = None,
129
+ description: str = None, ) -> 'Attachment':
130
+ """
131
+ [async] Create a new Attachment.
132
+
133
+ Args:
134
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
135
+ name (str): The name of the scene.
136
+ loc_x (int): x parameter of the attachment location.
137
+ loc_y (int): y parameter of the attachment location.
138
+ resource (Map | VectorLayer | VectorLayerView): the resource object.
139
+ file (File): the file object.
140
+ feature (Feature, optional): the feature object.
141
+ display_name (str, optional): The display name of the scene.
142
+ description (str, optional): The description of the scene.
143
+
144
+ Returns:
145
+ Attachment: The newly created Attachment instance.
146
+
147
+ Raises:
148
+ ValidationError: If the Attachment data is invalid.
149
+
150
+ Example:
151
+ >>> from geobox.aio import AsyncGeoboxClient
152
+ >>> from geobox.aio.attachment import Attachment
153
+ >>> async with AsyncGeoboxClient() as client:
154
+ >>> layer = await client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
155
+ >>> feature = await layer.get_feature(feature_id=1)
156
+ >>> file = await client.get_file(uuid="12345678-1234-5678-1234-567812345678")
157
+ >>> attachment = await Attachment.create_attachment(client,
158
+ ... name="my_attachment",
159
+ ... loc_x=30,
160
+ ... loc_y=50,
161
+ ... resource=layer,
162
+ ... file=file,
163
+ ... feature=feature,
164
+ ... display_name="My Attachment",
165
+ ... description="Attachment Description")
166
+ or
167
+ >>> attachment = await client.create_attachment(name="my_attachment",
168
+ ... loc_x=30,
169
+ ... loc_y=50,
170
+ ... resource=layer,
171
+ ... file=file,
172
+ ... feature=feature,
173
+ ... display_name="My Attachment",
174
+ ... description="Attachment Description")
175
+ """
176
+ if isinstance(resource, VectorLayer):
177
+ resource_type = 'vector'
178
+
179
+ if isinstance(resource, VectorLayerView):
180
+ resource_type = 'view'
181
+
182
+ if isinstance(resource, Map):
183
+ resource_type = 'map'
184
+
185
+ data = {
186
+ "name": name,
187
+ "display_name": display_name,
188
+ "description": description,
189
+ "loc_x": loc_x,
190
+ "loc_y": loc_y,
191
+ "resource_type": resource_type,
192
+ "resource_uuid": resource.uuid,
193
+ "element_id": feature.id if feature else None,
194
+ "file_id": file.id
195
+ }
196
+ return await super()._create(api, cls.BASE_ENDPOINT, data, factory_func=lambda api, item: Attachment(api, item['id'], item))
197
+
198
+
199
+ @classmethod
200
+ async def update_attachment(cls, api: 'AsyncGeoboxClient', attachment_id: int, **kwargs) -> Dict:
201
+ """
202
+ [async] Update the attachment.
203
+
204
+ Args:
205
+ api (AsyncGeoboxClient): The AsyncGeoboxClient instance for making requests.
206
+ attachment_id (int): the attachment id.
207
+
208
+ Keyword Args:
209
+ name (str): The name of the attachment.
210
+ display_name (str): The display name of the attachment.
211
+ description (str): The description of the attachment.
212
+ loc_x (int): x parameter of the attachment location.
213
+ loc_y (int): y parameter of the attachment location.
214
+
215
+ Returns:
216
+ Dict: The updated attachment data.
217
+
218
+ Raises:
219
+ ValidationError: If the attachment data is invalid.
220
+
221
+ Example:
222
+ >>> from geobox.aio import AsyncGeoboxClient
223
+ >>> from geobox.aio.attachment import Attachment
224
+ >>> async with AsyncGeoboxClient() as client:
225
+ >>> await Attachment.update_attachment(client, attachment_id=1, display_name="New Display Name")
226
+ or
227
+ >>> await client.update_attachment(attachment_id=1, display_name="New Display Name")
228
+ """
229
+ data = clean_data({
230
+ "name": kwargs.get('name'),
231
+ "display_name": kwargs.get('display_name'),
232
+ "description": kwargs.get('description'),
233
+ "loc_x": kwargs.get('loc_x'),
234
+ "loc_y": kwargs.get('loc_y')
235
+ })
236
+ endpoint = urljoin(cls.BASE_ENDPOINT, str(attachment_id))
237
+ response = await api.put(endpoint, data)
238
+ return response
239
+
240
+
241
+ async def update(self, **kwargs) -> Dict:
242
+ """
243
+ [async] Update the attachment.
244
+
245
+ Keyword Args:
246
+ name (str): The name of the attachment.
247
+ display_name (str): The display name of the attachment.
248
+ description (str): The description of the attachment.
249
+ loc_x (int): x parameter of the attachment location.
250
+ loc_y (int): y parameter of the attachment location.
251
+
252
+ Returns:
253
+ Dict: The updated attachment data.
254
+
255
+ Raises:
256
+ ValidationError: If the attachment data is invalid.
257
+
258
+ Example:
259
+ >>> from geobox.aio import AsyncGeoboxClient
260
+ >>> from geobox.aio.attachment import Attachment
261
+ >>> async with AsyncGeoboxClient() as client:
262
+ >>> attachment = await Attachment.get_attachments(client)[0]
263
+ >>> await attachment.update(display_name="New Display Name")
264
+ """
265
+ data = clean_data({
266
+ "name": kwargs.get('name'),
267
+ "display_name": kwargs.get('display_name'),
268
+ "description": kwargs.get('description'),
269
+ "loc_x": kwargs.get('loc_x'),
270
+ "loc_y": kwargs.get('loc_y')
271
+ })
272
+ response = await self.api.put(self.endpoint, data)
273
+ self._update_properties(response)
274
+ return response
275
+
276
+
277
+ async def delete(self) -> None:
278
+ """
279
+ [async] Delete the scene.
280
+
281
+ Returns:
282
+ None
283
+
284
+ Raises:
285
+ ApiRequestError: If the API request fails.
286
+ ValidationError: If the scene data is invalid.
287
+
288
+ Example:
289
+ >>> from geobox.aio import AsyncGeoboxClient
290
+ >>> from geobox.aio.attachment import Attachment
291
+ >>> async with AsyncGeoboxClient() as client:
292
+ >>> attachment = await Attachment.get_attachments(client)[0]
293
+ >>> await attachment.delete()
294
+ """
295
+ await super().delete(self.endpoint)
296
+ self.attachment_id = None
297
+
298
+
299
+ @property
300
+ def thumbnail(self) -> str:
301
+ """
302
+ Get the thumbnail URL of the attachment.
303
+
304
+ Returns:
305
+ str: The thumbnail of the scene.
306
+
307
+ Example:
308
+ >>> from geobox.aio import AsyncGeoboxClient
309
+ >>> from geobox.aio.attachment import Attachment
310
+ >>> async with AsyncGeoboxClient() as client:
311
+ >>> attachment = await Attachment.get_attachment(client, uuid="12345678-1234-5678-1234-567812345678")
312
+ >>> attachment.thumbnail
313
+ 'https://example.com/thumbnail.png'
314
+ """
315
+ return super().thumbnail(format='')
316
+
317
+
318
+ def to_sync(self, sync_client: 'SyncGeoboxClient') -> 'SyncAtachment':
319
+ """
320
+ Switch to sync version of the attachment instance to have access to the sync methods
321
+
322
+ Args:
323
+ sync_client (SyncGeoboxClient): The sync version of the GeoboxClient instance for making requests.
324
+
325
+ Returns:
326
+ geobox.attachment.Atachment: the sync instance of the attachment.
327
+
328
+ Example:
329
+ >>> from geobox import Geoboxclient
330
+ >>> from geobox.aio import AsyncGeoboxClient
331
+ >>> from geobox.aio.attachment import Attachment
332
+ >>> client = GeoboxClient()
333
+ >>> async with AsyncGeoboxClient() as async_client:
334
+ >>> attachment = await Attachment.get_attachment(async_client, uuid="12345678-1234-5678-1234-567812345678")
335
+ >>> sync_attachment = attachment.to_sync(client)
336
+ """
337
+ from ..attachment import Attachment as SyncAttachment
338
+
339
+ return SyncAttachment(api=sync_client, attachment_id=self.attachment_id, data=self.data)