pygeobox 1.0.1__py3-none-any.whl → 1.0.2__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.
- pygeobox/__init__.py +63 -63
- pygeobox/api.py +2517 -2517
- pygeobox/apikey.py +232 -232
- pygeobox/attachment.py +297 -297
- pygeobox/base.py +364 -364
- pygeobox/basemap.py +162 -162
- pygeobox/dashboard.py +316 -316
- pygeobox/enums.py +354 -354
- pygeobox/exception.py +47 -47
- pygeobox/field.py +309 -309
- pygeobox/map.py +858 -858
- pygeobox/model3d.py +334 -334
- pygeobox/mosaic.py +647 -647
- pygeobox/plan.py +260 -260
- pygeobox/query.py +668 -668
- pygeobox/raster.py +756 -756
- pygeobox/route.py +63 -63
- pygeobox/scene.py +317 -317
- pygeobox/settings.py +165 -165
- pygeobox/task.py +354 -354
- pygeobox/tile3d.py +294 -294
- pygeobox/tileset.py +585 -585
- pygeobox/user.py +423 -423
- pygeobox/utils.py +43 -43
- pygeobox/vectorlayer.py +1149 -1149
- pygeobox/version.py +248 -248
- pygeobox/view.py +859 -859
- pygeobox/workflow.py +315 -315
- {pygeobox-1.0.1.dist-info → pygeobox-1.0.2.dist-info}/METADATA +18 -24
- pygeobox-1.0.2.dist-info/RECORD +37 -0
- {pygeobox-1.0.1.dist-info → pygeobox-1.0.2.dist-info}/licenses/LICENSE +21 -21
- pygeobox-1.0.1.dist-info/RECORD +0 -37
- {pygeobox-1.0.1.dist-info → pygeobox-1.0.2.dist-info}/WHEEL +0 -0
- {pygeobox-1.0.1.dist-info → pygeobox-1.0.2.dist-info}/top_level.txt +0 -0
pygeobox/base.py
CHANGED
@@ -1,364 +1,364 @@
|
|
1
|
-
from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union
|
2
|
-
from urllib.parse import urljoin, urlencode
|
3
|
-
from datetime import datetime
|
4
|
-
|
5
|
-
from .utils import clean_data
|
6
|
-
|
7
|
-
if TYPE_CHECKING:
|
8
|
-
from .user import User
|
9
|
-
from .task import Task
|
10
|
-
from . import GeoboxClient
|
11
|
-
|
12
|
-
|
13
|
-
class Base:
|
14
|
-
BASE_ENDPOINT = ''
|
15
|
-
|
16
|
-
def __init__(self, api, **kwargs):
|
17
|
-
"""
|
18
|
-
Initialize the Base class.
|
19
|
-
|
20
|
-
Args:
|
21
|
-
api (GeoboxClient): The API client
|
22
|
-
uuid (str, optional): The UUID of the resource
|
23
|
-
data (dict, optional): The data of the resource
|
24
|
-
"""
|
25
|
-
self.api = api
|
26
|
-
self.uuid = kwargs.get('uuid')
|
27
|
-
self.data = kwargs.get('data')
|
28
|
-
self.endpoint = urljoin(self.BASE_ENDPOINT, f'{self.uuid}/') if self.uuid else None
|
29
|
-
|
30
|
-
|
31
|
-
def __dir__(self) -> List[str]:
|
32
|
-
"""
|
33
|
-
Return a list of available attributes for the Feature object.
|
34
|
-
|
35
|
-
This method extends the default dir() behavior to include:
|
36
|
-
- All keys from the data dictionary
|
37
|
-
|
38
|
-
This allows for better IDE autocompletion and introspection of feature attributes.
|
39
|
-
|
40
|
-
Returns:
|
41
|
-
list: A list of attribute names available on this object.
|
42
|
-
"""
|
43
|
-
return super().__dir__() + list(self.data.keys())
|
44
|
-
|
45
|
-
|
46
|
-
def __getattr__(self, name: str) -> Any:
|
47
|
-
"""
|
48
|
-
Get an attribute from the resource.
|
49
|
-
|
50
|
-
Args:
|
51
|
-
name (str): The name of the attribute
|
52
|
-
"""
|
53
|
-
if name in self.data:
|
54
|
-
value = self.data.get(name)
|
55
|
-
if isinstance(value, str):
|
56
|
-
parsed = self._parse_datetime(value)
|
57
|
-
if isinstance(parsed, datetime):
|
58
|
-
return parsed
|
59
|
-
return value
|
60
|
-
raise AttributeError(f"{self.__class__.__name__} has no attribute {name}")
|
61
|
-
|
62
|
-
|
63
|
-
def __repr__(self) -> str:
|
64
|
-
"""
|
65
|
-
Return a string representation of the resource.
|
66
|
-
"""
|
67
|
-
return f"{self.__class__.__name__}(name={self.name})"
|
68
|
-
|
69
|
-
|
70
|
-
def _update_properties(self, data: dict) -> None:
|
71
|
-
"""
|
72
|
-
Update the properties of the resource.
|
73
|
-
|
74
|
-
Args:
|
75
|
-
data (dict): The data to update the properties with
|
76
|
-
"""
|
77
|
-
self.data.update(data)
|
78
|
-
|
79
|
-
|
80
|
-
@classmethod
|
81
|
-
def _handle_geojson_response(cls, api: 'GeoboxClient', response: dict, factory_func: Callable) -> List['Base']:
|
82
|
-
"""Handle GeoJSON response format"""
|
83
|
-
features_data = response.get('features', [])
|
84
|
-
srid = cls._extract_srid(response)
|
85
|
-
return [factory_func(api, feature, srid) for feature in features_data]
|
86
|
-
|
87
|
-
|
88
|
-
@classmethod
|
89
|
-
def _extract_srid(cls, response: dict) -> int:
|
90
|
-
"""Extract SRID from GeoJSON response"""
|
91
|
-
if isinstance(response, dict) and 'crs' in response:
|
92
|
-
return int(response['crs']['properties']['name'].split(':')[-1])
|
93
|
-
|
94
|
-
|
95
|
-
@classmethod
|
96
|
-
def _get_count(cls, response: dict) -> int:
|
97
|
-
"""Get the count of resources"""
|
98
|
-
if isinstance(response, dict) and 'count' in response:
|
99
|
-
return response.get('count', 0)
|
100
|
-
elif isinstance(response, int):
|
101
|
-
return response
|
102
|
-
else:
|
103
|
-
raise ValueError('Invalid response format')
|
104
|
-
|
105
|
-
|
106
|
-
@classmethod
|
107
|
-
def _get_list(cls, api: 'GeoboxClient', endpoint: str, params: dict = {},
|
108
|
-
factory_func: Callable = None, geojson: bool = False) -> Union[List['Base'], int]:
|
109
|
-
"""Get a list of resources with optional filtering and pagination"""
|
110
|
-
query_string = urlencode(clean_data(params)) if params else ''
|
111
|
-
endpoint = urljoin(endpoint, f'?{query_string}')
|
112
|
-
response = api.get(endpoint)
|
113
|
-
|
114
|
-
if params.get('return_count'):
|
115
|
-
return cls._get_count(response)
|
116
|
-
|
117
|
-
if not response:
|
118
|
-
return []
|
119
|
-
|
120
|
-
if geojson:
|
121
|
-
return cls._handle_geojson_response(api, response, factory_func)
|
122
|
-
|
123
|
-
return [factory_func(api, item) for item in response]
|
124
|
-
|
125
|
-
|
126
|
-
@classmethod
|
127
|
-
def _get_list_by_ids(cls, api: 'GeoboxClient', endpoint: str, params: dict = None, factory_func: Callable = None) -> List['Base']:
|
128
|
-
"""
|
129
|
-
Internal method to get a list of resources by their IDs.
|
130
|
-
|
131
|
-
Args:
|
132
|
-
api (GeoboxClient): The API client
|
133
|
-
endpoint (str): The endpoint of the resource
|
134
|
-
params (dict): Additional parameters for filtering and pagination
|
135
|
-
factory_func (Callable): A function to create the resource object
|
136
|
-
|
137
|
-
Returns:
|
138
|
-
List[Base]: The list of resource objects
|
139
|
-
"""
|
140
|
-
params = clean_data(params)
|
141
|
-
query_string = urlencode(params)
|
142
|
-
endpoint = urljoin(endpoint, f'?{query_string}')
|
143
|
-
response = api.get(endpoint)
|
144
|
-
return [factory_func(api, item) for item in response]
|
145
|
-
|
146
|
-
|
147
|
-
@classmethod
|
148
|
-
def _get_detail(cls, api: 'GeoboxClient', endpoint: str, uuid: str, params: dict = {}, factory_func: Callable = None) -> 'Base':
|
149
|
-
"""
|
150
|
-
Internal method to get a single resource by UUID.
|
151
|
-
|
152
|
-
Args:
|
153
|
-
api (GeoboxClient): The API client
|
154
|
-
uuid (str): The UUID of the resource
|
155
|
-
params (dict): Additional parameters for filtering and pagination
|
156
|
-
factory_func (Callable): A function to create the resource object
|
157
|
-
|
158
|
-
Returns:
|
159
|
-
Base: The resource object
|
160
|
-
"""
|
161
|
-
query_strings = urlencode(clean_data(params))
|
162
|
-
endpoint = urljoin(endpoint, f'{uuid}/?{query_strings}')
|
163
|
-
response = api.get(endpoint)
|
164
|
-
return factory_func(api, response)
|
165
|
-
|
166
|
-
|
167
|
-
@classmethod
|
168
|
-
def _create(cls, api: 'GeoboxClient', endpoint: str, data: dict, factory_func: Callable = None) -> 'Base':
|
169
|
-
"""
|
170
|
-
Internal method to create a resource.
|
171
|
-
|
172
|
-
Args:
|
173
|
-
api (GeoboxClient): The API client
|
174
|
-
data (dict): The data to create the resource with
|
175
|
-
factory_func (Callable): A function to create the resource object
|
176
|
-
|
177
|
-
Returns:
|
178
|
-
Base: The created resource object
|
179
|
-
"""
|
180
|
-
data = clean_data(data)
|
181
|
-
response = api.post(endpoint, data)
|
182
|
-
return factory_func(api, response)
|
183
|
-
|
184
|
-
|
185
|
-
def _update(self, endpoint: str, data: dict, clean: bool = True) -> Dict:
|
186
|
-
"""
|
187
|
-
Update the resource.
|
188
|
-
|
189
|
-
Args:
|
190
|
-
data (dict): The data to update the resource with
|
191
|
-
"""
|
192
|
-
if clean:
|
193
|
-
data = clean_data(data)
|
194
|
-
|
195
|
-
response = self.api.put(endpoint, data)
|
196
|
-
self._update_properties(response)
|
197
|
-
return response
|
198
|
-
|
199
|
-
|
200
|
-
def delete(self, endpoint: str) -> None:
|
201
|
-
"""
|
202
|
-
Delete the resource.
|
203
|
-
"""
|
204
|
-
self.api.delete(endpoint)
|
205
|
-
self.uuid = None
|
206
|
-
self.endpoint = None
|
207
|
-
|
208
|
-
|
209
|
-
def _share(self, endpoint: str, users: List['User']) -> None:
|
210
|
-
"""
|
211
|
-
Internal method to share the resource with the given user IDs.
|
212
|
-
|
213
|
-
Args:
|
214
|
-
users (List[User]): The user objects to share the resource with
|
215
|
-
"""
|
216
|
-
data = {"user_ids": [user.user_id for user in users]}
|
217
|
-
endpoint = urljoin(endpoint, f'share/')
|
218
|
-
self.api.post(endpoint, data, is_json=False)
|
219
|
-
|
220
|
-
|
221
|
-
def _unshare(self, endpoint: str, users: List['User']) -> None:
|
222
|
-
"""
|
223
|
-
Internal method to unshare the resource with the given user IDs.
|
224
|
-
|
225
|
-
Args:
|
226
|
-
users (List[User]): The user objects to unshare the resource with
|
227
|
-
"""
|
228
|
-
data = {"user_ids": [user.user_id for user in users]}
|
229
|
-
endpoint = urljoin(endpoint, f'unshare/')
|
230
|
-
self.api.post(endpoint, data, is_json=False)
|
231
|
-
|
232
|
-
|
233
|
-
def _get_shared_users(self, endpoint: str, params: dict = None) -> List['User']:
|
234
|
-
"""
|
235
|
-
Internal method to get the users that the resource is shared with.
|
236
|
-
|
237
|
-
Args:
|
238
|
-
endpoint (str): resource endpoint
|
239
|
-
params (dict): Additional parameters for filtering and pagination
|
240
|
-
|
241
|
-
Returns:
|
242
|
-
List[User]: The users that the resource is shared with
|
243
|
-
"""
|
244
|
-
from .user import User
|
245
|
-
|
246
|
-
params = clean_data(params)
|
247
|
-
query_strings = urlencode(params)
|
248
|
-
endpoint = urljoin(endpoint, f'shared-with-users/?{query_strings}')
|
249
|
-
response = self.api.get(endpoint)
|
250
|
-
return [User(self.api, item['id'], item) for item in response]
|
251
|
-
|
252
|
-
|
253
|
-
def _get_settings(self, endpoint: str) -> Dict:
|
254
|
-
"""
|
255
|
-
Internal method to get the settings of the resource.
|
256
|
-
|
257
|
-
Args:
|
258
|
-
endpoint (str): The endpoint of the resource
|
259
|
-
"""
|
260
|
-
endpoint = urljoin(endpoint, f'settings/?f=json')
|
261
|
-
return self.api.get(endpoint)
|
262
|
-
|
263
|
-
|
264
|
-
def _set_settings(self, endpoint: str, data: dict) -> None:
|
265
|
-
"""
|
266
|
-
Internal method to set the settings of the resource.
|
267
|
-
|
268
|
-
Args:
|
269
|
-
endpoint (str): The endpoint of the resource
|
270
|
-
data (dict): The data to set the settings with
|
271
|
-
"""
|
272
|
-
endpoint = urljoin(endpoint, f'settings/')
|
273
|
-
return self.api.put(endpoint, data)
|
274
|
-
|
275
|
-
|
276
|
-
def _get_task(self, response, error_message: str) -> List['Task']:
|
277
|
-
from .task import Task # avoid circular dependency
|
278
|
-
|
279
|
-
if len(response) == 1 and isinstance(response, list) and response[0].get('task_id'):
|
280
|
-
result = [self.api.get_task(response[0].get('task_id'))]
|
281
|
-
elif len(response) == 2 and isinstance(response, list) and (response[0].get('task_id') and response[1].get('task_id')):
|
282
|
-
result = [self.api.get_task(item.get('task_id')) for item in response]
|
283
|
-
elif len(response) == 1 and isinstance(response, dict) and response.get('task_id'):
|
284
|
-
result = [self.api.get_task(response.get('task_id'))]
|
285
|
-
else:
|
286
|
-
raise ValueError(error_message)
|
287
|
-
|
288
|
-
return result
|
289
|
-
|
290
|
-
|
291
|
-
def _seed_cache(self, endpoint: str, data: dict) -> List['Task']:
|
292
|
-
"""
|
293
|
-
Internal method to cache seed the resource.
|
294
|
-
|
295
|
-
Args:
|
296
|
-
endpoint (str): The endpoint of the resource
|
297
|
-
data (dict): The data to cache seed with
|
298
|
-
"""
|
299
|
-
if data['workers'] not in [1, 2, 4, 8, 12, 16, 20, 24]:
|
300
|
-
raise ValueError("workers must be in [1, 2, 4, 8, 12, 16, 20, 24]")
|
301
|
-
|
302
|
-
data = clean_data(data)
|
303
|
-
endpoint = urljoin(endpoint, f'cache/seed/')
|
304
|
-
response = self.api.post(endpoint, data)
|
305
|
-
return self._get_task(response, 'Failed to seed cache')
|
306
|
-
|
307
|
-
def _clear_cache(self, endpoint: str) -> None:
|
308
|
-
"""
|
309
|
-
Internal method to clear the cache of the resource.
|
310
|
-
|
311
|
-
Args:
|
312
|
-
endpoint (str): The endpoint of the resource
|
313
|
-
"""
|
314
|
-
endpoint = urljoin(endpoint, f'cache/clear/')
|
315
|
-
self.api.post(endpoint)
|
316
|
-
|
317
|
-
|
318
|
-
def _cache_size(self, endpoint: str) -> int:
|
319
|
-
"""
|
320
|
-
Internal method to get the size of the cache of the resource.
|
321
|
-
|
322
|
-
Args:
|
323
|
-
endpoint (str): The endpoint of the resource
|
324
|
-
"""
|
325
|
-
endpoint = urljoin(endpoint, f'cache/size/')
|
326
|
-
return self.api.post(endpoint)
|
327
|
-
|
328
|
-
|
329
|
-
def _update_cache(self, endpoint: str, data: Dict = {}) -> List['Task']:
|
330
|
-
"""
|
331
|
-
Internal method to update the cache of the resource.
|
332
|
-
|
333
|
-
Args:
|
334
|
-
endpoint (str): The endpoint of the resource
|
335
|
-
"""
|
336
|
-
data = clean_data(data)
|
337
|
-
endpoint = urljoin(endpoint, 'cache/update/')
|
338
|
-
response = self.api.post(endpoint, data)
|
339
|
-
return self._get_task(response, 'Failed to update cache')
|
340
|
-
|
341
|
-
def _parse_datetime(self, date_string: str) -> Union[datetime, str]:
|
342
|
-
"""
|
343
|
-
Parse a datetime string with multiple format support.
|
344
|
-
|
345
|
-
Args:
|
346
|
-
date_string (str): The datetime string to parse
|
347
|
-
|
348
|
-
Returns:
|
349
|
-
Union[datetime, str]: Parsed datetime object or original string if parsing fails
|
350
|
-
"""
|
351
|
-
formats = [
|
352
|
-
"%Y-%m-%dT%H:%M:%S.%f", # With microseconds
|
353
|
-
"%Y-%m-%dT%H:%M:%SZ", # Without microseconds, with timezone
|
354
|
-
"%Y-%m-%dT%H:%M:%S" # Without microseconds, without timezone
|
355
|
-
]
|
356
|
-
|
357
|
-
for fmt in formats:
|
358
|
-
try:
|
359
|
-
return datetime.strptime(date_string, fmt)
|
360
|
-
except ValueError:
|
361
|
-
continue
|
362
|
-
|
363
|
-
# If all parsing fails, return the original string
|
364
|
-
return date_string
|
1
|
+
from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union
|
2
|
+
from urllib.parse import urljoin, urlencode
|
3
|
+
from datetime import datetime
|
4
|
+
|
5
|
+
from .utils import clean_data
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from .user import User
|
9
|
+
from .task import Task
|
10
|
+
from . import GeoboxClient
|
11
|
+
|
12
|
+
|
13
|
+
class Base:
|
14
|
+
BASE_ENDPOINT = ''
|
15
|
+
|
16
|
+
def __init__(self, api, **kwargs):
|
17
|
+
"""
|
18
|
+
Initialize the Base class.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
api (GeoboxClient): The API client
|
22
|
+
uuid (str, optional): The UUID of the resource
|
23
|
+
data (dict, optional): The data of the resource
|
24
|
+
"""
|
25
|
+
self.api = api
|
26
|
+
self.uuid = kwargs.get('uuid')
|
27
|
+
self.data = kwargs.get('data')
|
28
|
+
self.endpoint = urljoin(self.BASE_ENDPOINT, f'{self.uuid}/') if self.uuid else None
|
29
|
+
|
30
|
+
|
31
|
+
def __dir__(self) -> List[str]:
|
32
|
+
"""
|
33
|
+
Return a list of available attributes for the Feature object.
|
34
|
+
|
35
|
+
This method extends the default dir() behavior to include:
|
36
|
+
- All keys from the data dictionary
|
37
|
+
|
38
|
+
This allows for better IDE autocompletion and introspection of feature attributes.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
list: A list of attribute names available on this object.
|
42
|
+
"""
|
43
|
+
return super().__dir__() + list(self.data.keys())
|
44
|
+
|
45
|
+
|
46
|
+
def __getattr__(self, name: str) -> Any:
|
47
|
+
"""
|
48
|
+
Get an attribute from the resource.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
name (str): The name of the attribute
|
52
|
+
"""
|
53
|
+
if name in self.data:
|
54
|
+
value = self.data.get(name)
|
55
|
+
if isinstance(value, str):
|
56
|
+
parsed = self._parse_datetime(value)
|
57
|
+
if isinstance(parsed, datetime):
|
58
|
+
return parsed
|
59
|
+
return value
|
60
|
+
raise AttributeError(f"{self.__class__.__name__} has no attribute {name}")
|
61
|
+
|
62
|
+
|
63
|
+
def __repr__(self) -> str:
|
64
|
+
"""
|
65
|
+
Return a string representation of the resource.
|
66
|
+
"""
|
67
|
+
return f"{self.__class__.__name__}(name={self.name})"
|
68
|
+
|
69
|
+
|
70
|
+
def _update_properties(self, data: dict) -> None:
|
71
|
+
"""
|
72
|
+
Update the properties of the resource.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
data (dict): The data to update the properties with
|
76
|
+
"""
|
77
|
+
self.data.update(data)
|
78
|
+
|
79
|
+
|
80
|
+
@classmethod
|
81
|
+
def _handle_geojson_response(cls, api: 'GeoboxClient', response: dict, factory_func: Callable) -> List['Base']:
|
82
|
+
"""Handle GeoJSON response format"""
|
83
|
+
features_data = response.get('features', [])
|
84
|
+
srid = cls._extract_srid(response)
|
85
|
+
return [factory_func(api, feature, srid) for feature in features_data]
|
86
|
+
|
87
|
+
|
88
|
+
@classmethod
|
89
|
+
def _extract_srid(cls, response: dict) -> int:
|
90
|
+
"""Extract SRID from GeoJSON response"""
|
91
|
+
if isinstance(response, dict) and 'crs' in response:
|
92
|
+
return int(response['crs']['properties']['name'].split(':')[-1])
|
93
|
+
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def _get_count(cls, response: dict) -> int:
|
97
|
+
"""Get the count of resources"""
|
98
|
+
if isinstance(response, dict) and 'count' in response:
|
99
|
+
return response.get('count', 0)
|
100
|
+
elif isinstance(response, int):
|
101
|
+
return response
|
102
|
+
else:
|
103
|
+
raise ValueError('Invalid response format')
|
104
|
+
|
105
|
+
|
106
|
+
@classmethod
|
107
|
+
def _get_list(cls, api: 'GeoboxClient', endpoint: str, params: dict = {},
|
108
|
+
factory_func: Callable = None, geojson: bool = False) -> Union[List['Base'], int]:
|
109
|
+
"""Get a list of resources with optional filtering and pagination"""
|
110
|
+
query_string = urlencode(clean_data(params)) if params else ''
|
111
|
+
endpoint = urljoin(endpoint, f'?{query_string}')
|
112
|
+
response = api.get(endpoint)
|
113
|
+
|
114
|
+
if params.get('return_count'):
|
115
|
+
return cls._get_count(response)
|
116
|
+
|
117
|
+
if not response:
|
118
|
+
return []
|
119
|
+
|
120
|
+
if geojson:
|
121
|
+
return cls._handle_geojson_response(api, response, factory_func)
|
122
|
+
|
123
|
+
return [factory_func(api, item) for item in response]
|
124
|
+
|
125
|
+
|
126
|
+
@classmethod
|
127
|
+
def _get_list_by_ids(cls, api: 'GeoboxClient', endpoint: str, params: dict = None, factory_func: Callable = None) -> List['Base']:
|
128
|
+
"""
|
129
|
+
Internal method to get a list of resources by their IDs.
|
130
|
+
|
131
|
+
Args:
|
132
|
+
api (GeoboxClient): The API client
|
133
|
+
endpoint (str): The endpoint of the resource
|
134
|
+
params (dict): Additional parameters for filtering and pagination
|
135
|
+
factory_func (Callable): A function to create the resource object
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
List[Base]: The list of resource objects
|
139
|
+
"""
|
140
|
+
params = clean_data(params)
|
141
|
+
query_string = urlencode(params)
|
142
|
+
endpoint = urljoin(endpoint, f'?{query_string}')
|
143
|
+
response = api.get(endpoint)
|
144
|
+
return [factory_func(api, item) for item in response]
|
145
|
+
|
146
|
+
|
147
|
+
@classmethod
|
148
|
+
def _get_detail(cls, api: 'GeoboxClient', endpoint: str, uuid: str, params: dict = {}, factory_func: Callable = None) -> 'Base':
|
149
|
+
"""
|
150
|
+
Internal method to get a single resource by UUID.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
api (GeoboxClient): The API client
|
154
|
+
uuid (str): The UUID of the resource
|
155
|
+
params (dict): Additional parameters for filtering and pagination
|
156
|
+
factory_func (Callable): A function to create the resource object
|
157
|
+
|
158
|
+
Returns:
|
159
|
+
Base: The resource object
|
160
|
+
"""
|
161
|
+
query_strings = urlencode(clean_data(params))
|
162
|
+
endpoint = urljoin(endpoint, f'{uuid}/?{query_strings}')
|
163
|
+
response = api.get(endpoint)
|
164
|
+
return factory_func(api, response)
|
165
|
+
|
166
|
+
|
167
|
+
@classmethod
|
168
|
+
def _create(cls, api: 'GeoboxClient', endpoint: str, data: dict, factory_func: Callable = None) -> 'Base':
|
169
|
+
"""
|
170
|
+
Internal method to create a resource.
|
171
|
+
|
172
|
+
Args:
|
173
|
+
api (GeoboxClient): The API client
|
174
|
+
data (dict): The data to create the resource with
|
175
|
+
factory_func (Callable): A function to create the resource object
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
Base: The created resource object
|
179
|
+
"""
|
180
|
+
data = clean_data(data)
|
181
|
+
response = api.post(endpoint, data)
|
182
|
+
return factory_func(api, response)
|
183
|
+
|
184
|
+
|
185
|
+
def _update(self, endpoint: str, data: dict, clean: bool = True) -> Dict:
|
186
|
+
"""
|
187
|
+
Update the resource.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
data (dict): The data to update the resource with
|
191
|
+
"""
|
192
|
+
if clean:
|
193
|
+
data = clean_data(data)
|
194
|
+
|
195
|
+
response = self.api.put(endpoint, data)
|
196
|
+
self._update_properties(response)
|
197
|
+
return response
|
198
|
+
|
199
|
+
|
200
|
+
def delete(self, endpoint: str) -> None:
|
201
|
+
"""
|
202
|
+
Delete the resource.
|
203
|
+
"""
|
204
|
+
self.api.delete(endpoint)
|
205
|
+
self.uuid = None
|
206
|
+
self.endpoint = None
|
207
|
+
|
208
|
+
|
209
|
+
def _share(self, endpoint: str, users: List['User']) -> None:
|
210
|
+
"""
|
211
|
+
Internal method to share the resource with the given user IDs.
|
212
|
+
|
213
|
+
Args:
|
214
|
+
users (List[User]): The user objects to share the resource with
|
215
|
+
"""
|
216
|
+
data = {"user_ids": [user.user_id for user in users]}
|
217
|
+
endpoint = urljoin(endpoint, f'share/')
|
218
|
+
self.api.post(endpoint, data, is_json=False)
|
219
|
+
|
220
|
+
|
221
|
+
def _unshare(self, endpoint: str, users: List['User']) -> None:
|
222
|
+
"""
|
223
|
+
Internal method to unshare the resource with the given user IDs.
|
224
|
+
|
225
|
+
Args:
|
226
|
+
users (List[User]): The user objects to unshare the resource with
|
227
|
+
"""
|
228
|
+
data = {"user_ids": [user.user_id for user in users]}
|
229
|
+
endpoint = urljoin(endpoint, f'unshare/')
|
230
|
+
self.api.post(endpoint, data, is_json=False)
|
231
|
+
|
232
|
+
|
233
|
+
def _get_shared_users(self, endpoint: str, params: dict = None) -> List['User']:
|
234
|
+
"""
|
235
|
+
Internal method to get the users that the resource is shared with.
|
236
|
+
|
237
|
+
Args:
|
238
|
+
endpoint (str): resource endpoint
|
239
|
+
params (dict): Additional parameters for filtering and pagination
|
240
|
+
|
241
|
+
Returns:
|
242
|
+
List[User]: The users that the resource is shared with
|
243
|
+
"""
|
244
|
+
from .user import User
|
245
|
+
|
246
|
+
params = clean_data(params)
|
247
|
+
query_strings = urlencode(params)
|
248
|
+
endpoint = urljoin(endpoint, f'shared-with-users/?{query_strings}')
|
249
|
+
response = self.api.get(endpoint)
|
250
|
+
return [User(self.api, item['id'], item) for item in response]
|
251
|
+
|
252
|
+
|
253
|
+
def _get_settings(self, endpoint: str) -> Dict:
|
254
|
+
"""
|
255
|
+
Internal method to get the settings of the resource.
|
256
|
+
|
257
|
+
Args:
|
258
|
+
endpoint (str): The endpoint of the resource
|
259
|
+
"""
|
260
|
+
endpoint = urljoin(endpoint, f'settings/?f=json')
|
261
|
+
return self.api.get(endpoint)
|
262
|
+
|
263
|
+
|
264
|
+
def _set_settings(self, endpoint: str, data: dict) -> None:
|
265
|
+
"""
|
266
|
+
Internal method to set the settings of the resource.
|
267
|
+
|
268
|
+
Args:
|
269
|
+
endpoint (str): The endpoint of the resource
|
270
|
+
data (dict): The data to set the settings with
|
271
|
+
"""
|
272
|
+
endpoint = urljoin(endpoint, f'settings/')
|
273
|
+
return self.api.put(endpoint, data)
|
274
|
+
|
275
|
+
|
276
|
+
def _get_task(self, response, error_message: str) -> List['Task']:
|
277
|
+
from .task import Task # avoid circular dependency
|
278
|
+
|
279
|
+
if len(response) == 1 and isinstance(response, list) and response[0].get('task_id'):
|
280
|
+
result = [self.api.get_task(response[0].get('task_id'))]
|
281
|
+
elif len(response) == 2 and isinstance(response, list) and (response[0].get('task_id') and response[1].get('task_id')):
|
282
|
+
result = [self.api.get_task(item.get('task_id')) for item in response]
|
283
|
+
elif len(response) == 1 and isinstance(response, dict) and response.get('task_id'):
|
284
|
+
result = [self.api.get_task(response.get('task_id'))]
|
285
|
+
else:
|
286
|
+
raise ValueError(error_message)
|
287
|
+
|
288
|
+
return result
|
289
|
+
|
290
|
+
|
291
|
+
def _seed_cache(self, endpoint: str, data: dict) -> List['Task']:
|
292
|
+
"""
|
293
|
+
Internal method to cache seed the resource.
|
294
|
+
|
295
|
+
Args:
|
296
|
+
endpoint (str): The endpoint of the resource
|
297
|
+
data (dict): The data to cache seed with
|
298
|
+
"""
|
299
|
+
if data['workers'] not in [1, 2, 4, 8, 12, 16, 20, 24]:
|
300
|
+
raise ValueError("workers must be in [1, 2, 4, 8, 12, 16, 20, 24]")
|
301
|
+
|
302
|
+
data = clean_data(data)
|
303
|
+
endpoint = urljoin(endpoint, f'cache/seed/')
|
304
|
+
response = self.api.post(endpoint, data)
|
305
|
+
return self._get_task(response, 'Failed to seed cache')
|
306
|
+
|
307
|
+
def _clear_cache(self, endpoint: str) -> None:
|
308
|
+
"""
|
309
|
+
Internal method to clear the cache of the resource.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
endpoint (str): The endpoint of the resource
|
313
|
+
"""
|
314
|
+
endpoint = urljoin(endpoint, f'cache/clear/')
|
315
|
+
self.api.post(endpoint)
|
316
|
+
|
317
|
+
|
318
|
+
def _cache_size(self, endpoint: str) -> int:
|
319
|
+
"""
|
320
|
+
Internal method to get the size of the cache of the resource.
|
321
|
+
|
322
|
+
Args:
|
323
|
+
endpoint (str): The endpoint of the resource
|
324
|
+
"""
|
325
|
+
endpoint = urljoin(endpoint, f'cache/size/')
|
326
|
+
return self.api.post(endpoint)
|
327
|
+
|
328
|
+
|
329
|
+
def _update_cache(self, endpoint: str, data: Dict = {}) -> List['Task']:
|
330
|
+
"""
|
331
|
+
Internal method to update the cache of the resource.
|
332
|
+
|
333
|
+
Args:
|
334
|
+
endpoint (str): The endpoint of the resource
|
335
|
+
"""
|
336
|
+
data = clean_data(data)
|
337
|
+
endpoint = urljoin(endpoint, 'cache/update/')
|
338
|
+
response = self.api.post(endpoint, data)
|
339
|
+
return self._get_task(response, 'Failed to update cache')
|
340
|
+
|
341
|
+
def _parse_datetime(self, date_string: str) -> Union[datetime, str]:
|
342
|
+
"""
|
343
|
+
Parse a datetime string with multiple format support.
|
344
|
+
|
345
|
+
Args:
|
346
|
+
date_string (str): The datetime string to parse
|
347
|
+
|
348
|
+
Returns:
|
349
|
+
Union[datetime, str]: Parsed datetime object or original string if parsing fails
|
350
|
+
"""
|
351
|
+
formats = [
|
352
|
+
"%Y-%m-%dT%H:%M:%S.%f", # With microseconds
|
353
|
+
"%Y-%m-%dT%H:%M:%SZ", # Without microseconds, with timezone
|
354
|
+
"%Y-%m-%dT%H:%M:%S" # Without microseconds, without timezone
|
355
|
+
]
|
356
|
+
|
357
|
+
for fmt in formats:
|
358
|
+
try:
|
359
|
+
return datetime.strptime(date_string, fmt)
|
360
|
+
except ValueError:
|
361
|
+
continue
|
362
|
+
|
363
|
+
# If all parsing fails, return the original string
|
364
|
+
return date_string
|