geobox 1.4.1__tar.gz → 1.4.2__tar.gz
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-1.4.1 → geobox-1.4.2}/PKG-INFO +1 -1
- {geobox-1.4.1 → geobox-1.4.2}/geobox/map.py +22 -3
- {geobox-1.4.1 → geobox-1.4.2}/geobox/model3d.py +101 -16
- {geobox-1.4.1 → geobox-1.4.2}/geobox/mosaic.py +19 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/query.py +3 -1
- {geobox-1.4.1 → geobox-1.4.2}/geobox/raster.py +19 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/vectorlayer.py +19 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/view.py +19 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox.egg-info/PKG-INFO +1 -1
- {geobox-1.4.1 → geobox-1.4.2}/pyproject.toml +1 -1
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_map.py +9 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_model3d.py +152 -16
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_mosaic.py +9 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_raster.py +9 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_vectorlayer.py +10 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_view.py +10 -0
- {geobox-1.4.1 → geobox-1.4.2}/LICENSE +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/README.md +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/__init__.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/api.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/apikey.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/attachment.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/base.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/basemap.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/dashboard.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/enums.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/exception.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/feature.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/field.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/file.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/layout.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/log.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/plan.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/route.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/scene.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/settings.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/task.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/tile3d.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/tileset.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/usage.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/user.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/utils.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/version.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox/workflow.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox.egg-info/SOURCES.txt +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox.egg-info/dependency_links.txt +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox.egg-info/requires.txt +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/geobox.egg-info/top_level.txt +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/setup.cfg +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_api.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_apikey.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_attachment.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_basemap.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_dashboard.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_feature.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_field.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_file.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_layout.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_log.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_plan.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_query.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_route.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_scene.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_settings.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_task.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_tile3d.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_tileset.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_usage.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_user.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_version.py +0 -0
- {geobox-1.4.1 → geobox-1.4.2}/tests/test_workflow.py +0 -0
|
@@ -574,11 +574,30 @@ class Map(Base):
|
|
|
574
574
|
'view_settings': {},
|
|
575
575
|
'toc_settings': []
|
|
576
576
|
})
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def update_settings(self, settings: Dict) -> Dict:
|
|
580
|
+
"""
|
|
581
|
+
Update the settings
|
|
582
|
+
|
|
583
|
+
settings (Dict): settings dictionary
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
Dict: updated settings
|
|
587
|
+
|
|
588
|
+
Example:
|
|
589
|
+
>>> from geobox import GeoboxClient
|
|
590
|
+
>>> client = GeoboxClient()
|
|
591
|
+
>>> map1 = client.get_map(uuid="12345678-1234-5678-1234-567812345678")
|
|
592
|
+
>>> map2 = client.get_map(uuid="12345678-1234-5678-1234-567812345678")
|
|
593
|
+
>>> map1.update_settings(map2.settings)
|
|
594
|
+
"""
|
|
595
|
+
return super()._set_settings(self.endpoint, settings)
|
|
577
596
|
|
|
578
597
|
|
|
579
598
|
def set_settings(self, **kwargs) -> Dict:
|
|
580
599
|
"""
|
|
581
|
-
Set the settings of the map
|
|
600
|
+
Set the settings of the map using keywords
|
|
582
601
|
|
|
583
602
|
Keyword Args:
|
|
584
603
|
map_unit (str): 'latlng' | 'utm'.
|
|
@@ -736,7 +755,7 @@ class Map(Base):
|
|
|
736
755
|
if not response or json:
|
|
737
756
|
return response
|
|
738
757
|
else:
|
|
739
|
-
return [Model(self.api, model['obj'], model) for model in response['objects']]
|
|
758
|
+
return [Model(self.api, model['obj'], model) for model in response['objects'] if response.get('objects')]
|
|
740
759
|
|
|
741
760
|
|
|
742
761
|
def set_models(self, data: Dict) -> List['Model']:
|
|
@@ -771,7 +790,7 @@ class Map(Base):
|
|
|
771
790
|
"""
|
|
772
791
|
endpoint = urljoin(self.endpoint, 'models/')
|
|
773
792
|
response = self.api.put(endpoint, data)
|
|
774
|
-
return [Model(self.api, model['obj'], model) for model in response['objects']]
|
|
793
|
+
return [Model(self.api, model['obj'], model) for model in response['objects'] if response.get('objects')]
|
|
775
794
|
|
|
776
795
|
|
|
777
796
|
def add_model(self,
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
from typing import Dict, List, Optional, Optional, Union, TYPE_CHECKING
|
|
2
2
|
from urllib.parse import urljoin, urlencode
|
|
3
|
+
import os
|
|
4
|
+
import zipfile
|
|
5
|
+
import sys
|
|
6
|
+
import requests
|
|
7
|
+
import mimetypes
|
|
3
8
|
|
|
4
9
|
from .base import Base
|
|
5
10
|
from .exception import ApiRequestError
|
|
@@ -216,33 +221,113 @@ class Model(Base):
|
|
|
216
221
|
super().delete(self.endpoint)
|
|
217
222
|
|
|
218
223
|
|
|
219
|
-
def
|
|
224
|
+
def _get_save_path(self, save_path: str = None) -> str:
|
|
220
225
|
"""
|
|
221
|
-
Get the
|
|
226
|
+
Get the path where the file should be saved.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
save_path (str, optional): The path to save the file.
|
|
222
230
|
|
|
223
231
|
Returns:
|
|
224
|
-
|
|
232
|
+
str: The path where the file is saved.
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
ValueError: If save_path does not end with a '/'.
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
>>> from geobox import GeoboxClient
|
|
239
|
+
>>> from geobox.file import File
|
|
240
|
+
>>> from geobox import GeoboxClient
|
|
241
|
+
>>> client = GeoboxClient()
|
|
242
|
+
>>> file_path = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
243
|
+
"""
|
|
244
|
+
# If save_path is provided, check if it ends with a '/'
|
|
245
|
+
if save_path and save_path.endswith('/'):
|
|
246
|
+
return f'{save_path}'
|
|
247
|
+
|
|
248
|
+
if save_path and not save_path.endswith('/'):
|
|
249
|
+
raise ValueError("save_path must end with a '/'")
|
|
250
|
+
|
|
251
|
+
return os.getcwd()
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _create_progress_bar(self) -> 'tqdm':
|
|
255
|
+
"""Creates a progress bar for the task."""
|
|
256
|
+
try:
|
|
257
|
+
from tqdm.auto import tqdm
|
|
258
|
+
except ImportError:
|
|
259
|
+
from .api import logger
|
|
260
|
+
logger.warning("[tqdm] extra is required to show the progress bar. install with: pip insatll geobox[tqdm]")
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
return tqdm(unit="B",
|
|
264
|
+
total=int(self.size),
|
|
265
|
+
file=sys.stdout,
|
|
266
|
+
dynamic_ncols=True,
|
|
267
|
+
desc="Downloading",
|
|
268
|
+
unit_scale=True,
|
|
269
|
+
unit_divisor=1024,
|
|
270
|
+
ascii=True
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def download(self, save_path: str = None, progress_bar: bool = True) -> str:
|
|
275
|
+
"""
|
|
276
|
+
Download the 3D model, save it as a .glb file, zip it, and return the zip file path.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
save_path (str, optional): Directory where the file should be saved.
|
|
280
|
+
progress_bar (bool, optional): Whether to show a progress bar. Default: True
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
str: Path to the .zip file containing the .glb model.
|
|
225
284
|
|
|
226
285
|
Raises:
|
|
227
286
|
ApiRequestError: If the API request fails.
|
|
228
287
|
|
|
229
288
|
Example:
|
|
230
289
|
>>> from geobox import GeoboxClient
|
|
231
|
-
>>> from geobox.model3d import Model
|
|
232
290
|
>>> client = GeoboxClient()
|
|
233
|
-
>>> model =
|
|
234
|
-
>>>
|
|
235
|
-
>>> # Save the content to a file
|
|
236
|
-
>>> with open('model.gltf', 'wb') as f:
|
|
237
|
-
... f.write(content)
|
|
291
|
+
>>> model = client.get_models()[0]
|
|
292
|
+
>>> model.get_content()
|
|
238
293
|
"""
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
294
|
+
if not self.uuid:
|
|
295
|
+
raise ValueError("Model UUID is required to download content")
|
|
296
|
+
|
|
297
|
+
if self.data.get('obj'):
|
|
298
|
+
model = self.api.get_model(self.obj)
|
|
299
|
+
else:
|
|
300
|
+
model = self
|
|
301
|
+
|
|
302
|
+
save_path = model._get_save_path(save_path)
|
|
303
|
+
os.makedirs(save_path, exist_ok=True)
|
|
304
|
+
|
|
305
|
+
endpoint = urljoin(model.api.base_url, f"{model.endpoint}/content/")
|
|
306
|
+
with model.api.session.get(endpoint, stream=True) as response:
|
|
307
|
+
if response.status_code != 200:
|
|
308
|
+
raise ApiRequestError(f"Failed to get model content: {response.status_code}")
|
|
309
|
+
|
|
310
|
+
glb_filename = f"{model.name}.glb"
|
|
311
|
+
glb_path = os.path.join(save_path, glb_filename)
|
|
312
|
+
|
|
313
|
+
with open(glb_path, "wb") as f:
|
|
314
|
+
pbar = model._create_progress_bar() if progress_bar else None
|
|
315
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
316
|
+
f.write(chunk)
|
|
317
|
+
if pbar:
|
|
318
|
+
pbar.update(len(chunk))
|
|
319
|
+
pbar.refresh()
|
|
320
|
+
if pbar:
|
|
321
|
+
pbar.close()
|
|
322
|
+
|
|
323
|
+
zip_filename = f"{model.name}.zip"
|
|
324
|
+
zip_path = os.path.join(save_path, zip_filename)
|
|
325
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
326
|
+
zipf.write(glb_path, arcname=os.path.basename(glb_path))
|
|
327
|
+
|
|
328
|
+
os.remove(glb_path)
|
|
329
|
+
|
|
330
|
+
return os.path.abspath(zip_path)
|
|
246
331
|
|
|
247
332
|
|
|
248
333
|
@property
|
|
@@ -401,6 +401,25 @@ class Mosaic(Raster):
|
|
|
401
401
|
"""
|
|
402
402
|
return super().settings
|
|
403
403
|
|
|
404
|
+
|
|
405
|
+
def update_settings(self, settings: Dict) -> Dict:
|
|
406
|
+
"""
|
|
407
|
+
Update the settings
|
|
408
|
+
|
|
409
|
+
settings (Dict): settings dictionary
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Dict: updated settings
|
|
413
|
+
|
|
414
|
+
Example:
|
|
415
|
+
>>> from geobox import GeoboxClient
|
|
416
|
+
>>> client = GeoboxClient()
|
|
417
|
+
>>> mosaic1 = client.get_mosaic(uuid="12345678-1234-5678-1234-567812345678")
|
|
418
|
+
>>> mosaic2 = client.get_mosaic(uuid="12345678-1234-5678-1234-567812345678")
|
|
419
|
+
>>> mosaic1.update_settings(mosaic2.settings)
|
|
420
|
+
"""
|
|
421
|
+
return super().update_settings(settings)
|
|
422
|
+
|
|
404
423
|
|
|
405
424
|
def set_settings(self, **kwargs) -> None:
|
|
406
425
|
"""
|
|
@@ -179,7 +179,7 @@ class Query(Base):
|
|
|
179
179
|
|
|
180
180
|
|
|
181
181
|
@classmethod
|
|
182
|
-
def create_query(cls, api: 'GeoboxClient', name: str, display_name: str = None, sql: str = None, params: List = None) -> 'Query':
|
|
182
|
+
def create_query(cls, api: 'GeoboxClient', name: str, display_name: str = None, description:str = None, sql: str = None, params: List = None) -> 'Query':
|
|
183
183
|
"""
|
|
184
184
|
Creates a new query.
|
|
185
185
|
|
|
@@ -187,6 +187,7 @@ class Query(Base):
|
|
|
187
187
|
api (Api): The GeoboxClient instance for making requests.
|
|
188
188
|
name (str): The name of the query.
|
|
189
189
|
display_name (str, optional): The display name of the query.
|
|
190
|
+
description (str, optional): The description of the query.
|
|
190
191
|
sql (str, optional): The SQL statement for the query.
|
|
191
192
|
params (list, optional): The parameters for the SQL statement.
|
|
192
193
|
|
|
@@ -204,6 +205,7 @@ class Query(Base):
|
|
|
204
205
|
data = {
|
|
205
206
|
"name": name,
|
|
206
207
|
"display_name": display_name,
|
|
208
|
+
"description": description,
|
|
207
209
|
"sql": sql,
|
|
208
210
|
"params": params
|
|
209
211
|
}
|
|
@@ -641,6 +641,25 @@ class Raster(Base):
|
|
|
641
641
|
>>> raster.settings
|
|
642
642
|
"""
|
|
643
643
|
return super()._get_settings(self.endpoint)
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def update_settings(self, settings: Dict) -> Dict:
|
|
647
|
+
"""
|
|
648
|
+
Update the settings
|
|
649
|
+
|
|
650
|
+
settings (Dict): settings dictionary
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
Dict: updated settings
|
|
654
|
+
|
|
655
|
+
Example:
|
|
656
|
+
>>> from geobox import GeoboxClient
|
|
657
|
+
>>> client = GeoboxClient()
|
|
658
|
+
>>> raster1 = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
|
|
659
|
+
>>> raster2 = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
|
|
660
|
+
>>> raster1.update_settings(raster2.settings)
|
|
661
|
+
"""
|
|
662
|
+
return super()._set_settings(self.endpoint, settings)
|
|
644
663
|
|
|
645
664
|
|
|
646
665
|
def set_settings(self, **kwargs) -> None:
|
|
@@ -1062,6 +1062,25 @@ class VectorLayer(Base):
|
|
|
1062
1062
|
>>> setting = layer.setting
|
|
1063
1063
|
"""
|
|
1064
1064
|
return super()._get_settings(endpoint=self.endpoint)
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def update_settings(self, settings: Dict) -> Dict:
|
|
1068
|
+
"""
|
|
1069
|
+
Update the settings
|
|
1070
|
+
|
|
1071
|
+
settings (Dict): settings dictionary
|
|
1072
|
+
|
|
1073
|
+
Returns:
|
|
1074
|
+
Dict: updated settings
|
|
1075
|
+
|
|
1076
|
+
Example:
|
|
1077
|
+
>>> from geobox import GeoboxClient
|
|
1078
|
+
>>> client = GeoboxClient()
|
|
1079
|
+
>>> layer1 = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
|
1080
|
+
>>> layer2 = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
|
1081
|
+
>>> layer1.update_settings(layer2.settings)
|
|
1082
|
+
"""
|
|
1083
|
+
return super()._set_settings(self.endpoint, settings)
|
|
1065
1084
|
|
|
1066
1085
|
|
|
1067
1086
|
def set_settings(self, **kwargs) -> Dict:
|
|
@@ -722,6 +722,25 @@ class VectorLayerView(VectorLayer):
|
|
|
722
722
|
return super().settings
|
|
723
723
|
|
|
724
724
|
|
|
725
|
+
def update_settings(self, settings: Dict) -> Dict:
|
|
726
|
+
"""
|
|
727
|
+
Update the settings
|
|
728
|
+
|
|
729
|
+
settings (Dict): settings dictionary
|
|
730
|
+
|
|
731
|
+
Returns:
|
|
732
|
+
Dict: updated settings
|
|
733
|
+
|
|
734
|
+
Example:
|
|
735
|
+
>>> from geobox import GeoboxClient
|
|
736
|
+
>>> client = GeoboxClient()
|
|
737
|
+
>>> view1 = client.get_view(uuid="12345678-1234-5678-1234-567812345678")
|
|
738
|
+
>>> view2 = client.get_view(uuid="12345678-1234-5678-1234-567812345678")
|
|
739
|
+
>>> view1.update_settings(view2.settings)
|
|
740
|
+
"""
|
|
741
|
+
return super().update_settings(settings)
|
|
742
|
+
|
|
743
|
+
|
|
725
744
|
def set_settings(self, **kwargs) -> Dict:
|
|
726
745
|
"""
|
|
727
746
|
Set the settings of the Vector Layer.
|
|
@@ -302,6 +302,15 @@ def test_settings(api, mock_map_data):
|
|
|
302
302
|
assert settings == mock_map_data['settings']
|
|
303
303
|
|
|
304
304
|
|
|
305
|
+
def test_update_settings(api, mock_map_data):
|
|
306
|
+
map1 = Map(api, uuid=mock_map_data['uuid'], data=mock_map_data)
|
|
307
|
+
map2 = Map(api, uuid=mock_map_data['uuid'], data=mock_map_data)
|
|
308
|
+
settings = map2.settings
|
|
309
|
+
api.put.return_value = settings
|
|
310
|
+
map1.update_settings(map2.settings)
|
|
311
|
+
api.put.assert_called_once_with(f'{map1.endpoint}settings/', settings)
|
|
312
|
+
|
|
313
|
+
|
|
305
314
|
def test_set_settings(api, mock_map_data):
|
|
306
315
|
map = Map(api, uuid=mock_map_data['uuid'], data=mock_map_data)
|
|
307
316
|
settings = map.settings
|
|
@@ -131,25 +131,161 @@ def test_delete(api, mock_model_data):
|
|
|
131
131
|
api.delete.assert_called_once_with(endpoint)
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
import os
|
|
135
|
+
import pytest
|
|
136
|
+
from unittest.mock import MagicMock, patch
|
|
137
|
+
from geobox.file import File
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_get_save_path_variants(api, mock_model_data, tmp_path):
|
|
141
|
+
"""Test _get_save_path edge cases."""
|
|
135
142
|
model = Model(api, uuid=mock_model_data['uuid'], data=mock_model_data)
|
|
136
|
-
|
|
143
|
+
|
|
144
|
+
# Case: no save_path -> returns cwd
|
|
145
|
+
result = model._get_save_path()
|
|
146
|
+
assert result == os.getcwd()
|
|
147
|
+
|
|
148
|
+
# Case: save_path ends with '/' -> returns unchanged
|
|
149
|
+
path = str(tmp_path) + "/"
|
|
150
|
+
assert model._get_save_path(save_path=path) == path
|
|
151
|
+
|
|
152
|
+
# Case: save_path does NOT end with '/' -> raises ValueError
|
|
153
|
+
path_invalid = str(tmp_path)
|
|
154
|
+
with pytest.raises(ValueError, match="save_path must end with a '/'"):
|
|
155
|
+
model._get_save_path(save_path=path_invalid)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_create_progress_bar_import_error(api, mock_model_data):
|
|
159
|
+
"""Test _create_progress_bar when tqdm is missing."""
|
|
160
|
+
model = Model(api, uuid=mock_model_data['uuid'], data=mock_model_data)
|
|
161
|
+
model.size = 1234
|
|
162
|
+
|
|
163
|
+
import builtins
|
|
164
|
+
original_import = builtins.__import__
|
|
165
|
+
|
|
166
|
+
def mocked_import(name, *args, **kwargs):
|
|
167
|
+
if name == "tqdm.auto":
|
|
168
|
+
raise ImportError("No module named 'tqdm'")
|
|
169
|
+
return original_import(name, *args, **kwargs)
|
|
170
|
+
|
|
171
|
+
with patch("builtins.__import__", side_effect=mocked_import):
|
|
172
|
+
with patch("geobox.api.logger") as mock_logger:
|
|
173
|
+
pbar = model._create_progress_bar()
|
|
174
|
+
assert pbar is None
|
|
175
|
+
mock_logger.warning.assert_called_once()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_download_with_obj(api, mock_model_data, tmp_path):
|
|
179
|
+
"""Test download() when self.data['obj'] exists."""
|
|
180
|
+
# Make mock data have 'obj'
|
|
181
|
+
mock_model_data['obj'] = "object-id"
|
|
182
|
+
model = Model(api, uuid=mock_model_data['uuid'], data=mock_model_data)
|
|
183
|
+
model.name = "test_model"
|
|
184
|
+
model.endpoint = "models"
|
|
185
|
+
|
|
186
|
+
# Mock api.get_model() to return a model with a normal download
|
|
187
|
+
downloaded_model = MagicMock()
|
|
188
|
+
downloaded_model.api = api
|
|
189
|
+
downloaded_model.name = model.name
|
|
190
|
+
downloaded_model.endpoint = model.endpoint
|
|
191
|
+
downloaded_model.data = {}
|
|
192
|
+
downloaded_model._get_save_path = lambda sp: str(tmp_path) + "/"
|
|
193
|
+
downloaded_model._create_progress_bar = lambda: MagicMock()
|
|
194
|
+
|
|
195
|
+
api.get_model.return_value = downloaded_model
|
|
196
|
+
|
|
197
|
+
# Mock API response
|
|
137
198
|
mock_response = MagicMock()
|
|
138
|
-
mock_response.
|
|
139
|
-
mock_response.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
api.session
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
mock_response.__enter__.return_value.iter_content.return_value = [b"chunk"]
|
|
200
|
+
mock_response.__enter__.return_value.status_code = 200
|
|
201
|
+
mock_response.__exit__.return_value = None
|
|
202
|
+
|
|
203
|
+
with patch.object(api.session, "get", return_value=mock_response), \
|
|
204
|
+
patch("builtins.open", MagicMock()), \
|
|
205
|
+
patch("os.makedirs", return_value=None), \
|
|
206
|
+
patch("os.remove", return_value=None), \
|
|
207
|
+
patch("zipfile.ZipFile", MagicMock()):
|
|
208
|
+
zip_path = model.download()
|
|
209
|
+
assert zip_path.endswith(".zip")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_download_non_200(api, mock_model_data, tmp_path):
|
|
213
|
+
"""Test download() raises ApiRequestError on bad status code."""
|
|
214
|
+
model = Model(api, uuid=mock_model_data['uuid'], data=mock_model_data)
|
|
215
|
+
model.name = "test_model"
|
|
216
|
+
model.endpoint = "models"
|
|
217
|
+
|
|
218
|
+
mock_response = MagicMock()
|
|
219
|
+
mock_response.__enter__.return_value.status_code = 404
|
|
220
|
+
mock_response.__enter__.return_value.iter_content.return_value = []
|
|
221
|
+
mock_response.__exit__.return_value = None
|
|
222
|
+
|
|
223
|
+
with patch.object(api.session, "get", return_value=mock_response), \
|
|
224
|
+
patch("os.makedirs", return_value=None):
|
|
225
|
+
from geobox.api import ApiRequestError
|
|
226
|
+
with pytest.raises(ApiRequestError, match="Failed to get model content: 404"):
|
|
227
|
+
model.download(save_path=str(tmp_path)+"/")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_download_missing_uuid(api, mock_model_data):
|
|
231
|
+
"""Raise ValueError if UUID is missing."""
|
|
232
|
+
model = Model(api, uuid=None, data=mock_model_data) # uuid is None
|
|
233
|
+
model.name = "test_model"
|
|
234
|
+
model.endpoint = "models"
|
|
235
|
+
|
|
236
|
+
from geobox.api import ApiRequestError
|
|
149
237
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
238
|
+
with pytest.raises(ValueError, match="Model UUID is required to download content"):
|
|
239
|
+
model.download()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_create_progress_bar_normal(api, mock_model_data):
|
|
243
|
+
"""Test _create_progress_bar returns tqdm instance when tqdm is available."""
|
|
244
|
+
from tqdm.auto import tqdm
|
|
245
|
+
|
|
246
|
+
model = Model(api, uuid=mock_model_data['uuid'], data=mock_model_data)
|
|
247
|
+
model.size = 1024 # required for total=int(self.size)
|
|
248
|
+
|
|
249
|
+
pbar = model._create_progress_bar()
|
|
250
|
+
assert isinstance(pbar, tqdm)
|
|
251
|
+
assert pbar.total == 1024
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# def test_download_method_simple(api, mock_model_data, tmp_path):
|
|
255
|
+
# """Test File.download() method without manually setting headers."""
|
|
256
|
+
# model = Model(api, uuid=mock_model_data['uuid'], data=mock_model_data)
|
|
257
|
+
# model.name = "test_model" # ensure a name for filenames
|
|
258
|
+
# model.endpoint = "models"
|
|
259
|
+
|
|
260
|
+
# # Mock API response
|
|
261
|
+
# mock_response = MagicMock()
|
|
262
|
+
# mock_response.__enter__.return_value.iter_content.return_value = [b"chunk1", b"chunk2"]
|
|
263
|
+
# mock_response.__enter__.return_value.status_code = 200
|
|
264
|
+
# mock_response.__exit__.return_value = None
|
|
265
|
+
|
|
266
|
+
# # Patch all filesystem and network operations
|
|
267
|
+
# with patch.object(api.session, "get", return_value=mock_response), \
|
|
268
|
+
# patch("builtins.open", new_callable=MagicMock), \
|
|
269
|
+
# patch("os.makedirs", return_value=None), \
|
|
270
|
+
# patch("os.remove", return_value=None), \
|
|
271
|
+
# patch("zipfile.ZipFile") as mock_zip, \
|
|
272
|
+
# patch.object(model, "_create_progress_bar", return_value=MagicMock()) as mock_pbar:
|
|
273
|
+
|
|
274
|
+
# # Save to custom tmp_path
|
|
275
|
+
# save_dir = str(tmp_path) + "/"
|
|
276
|
+
# zip_path = model.download(save_path=save_dir)
|
|
277
|
+
|
|
278
|
+
# # Assertions
|
|
279
|
+
# assert zip_path.endswith(".zip")
|
|
280
|
+
# assert os.path.commonpath([zip_path, save_dir]) == os.path.normpath(save_dir)
|
|
281
|
+
# assert os.path.basename(zip_path) == "test_model.zip"
|
|
282
|
+
# mock_zip.assert_called_once() # ensure zipfile was called
|
|
283
|
+
# mock_pbar.return_value.update.assert_called() # ensure progress bar updated
|
|
284
|
+
|
|
285
|
+
# # Test that ValueError is raised if uuid is missing
|
|
286
|
+
# model.uuid = None
|
|
287
|
+
# with pytest.raises(ValueError, match="Model UUID is required to download content"):
|
|
288
|
+
# model.download(save_path=save_dir)
|
|
153
289
|
|
|
154
290
|
|
|
155
291
|
def test_thumbnail(api, mock_model_data):
|
|
@@ -184,6 +184,15 @@ def test_settings(api, mock_mosaic_data):
|
|
|
184
184
|
assert settings == {'value': 'test'}
|
|
185
185
|
|
|
186
186
|
|
|
187
|
+
def test_update_settings(api, mock_mosaic_data):
|
|
188
|
+
mosaic1 = Mosaic(api, uuid=mock_mosaic_data['uuid'], data=mock_mosaic_data)
|
|
189
|
+
mosaic2 = Mosaic(api, uuid=mock_mosaic_data['uuid'], data=mock_mosaic_data)
|
|
190
|
+
settings = mosaic2.settings
|
|
191
|
+
api.put.return_value = settings
|
|
192
|
+
mosaic1.update_settings(mosaic2.settings)
|
|
193
|
+
api.put.assert_called_once_with(f'{mosaic1.endpoint}settings/', settings)
|
|
194
|
+
|
|
195
|
+
|
|
187
196
|
def test_set_settings(api, mock_mosaic_data):
|
|
188
197
|
"""Test the set_settings method."""
|
|
189
198
|
mosaic = Mosaic(api, mock_mosaic_data['uuid'], mock_mosaic_data)
|
|
@@ -310,6 +310,15 @@ def test_settings(api, mock_raster_data):
|
|
|
310
310
|
api.get.assert_called_once_with(f'{raster.endpoint}settings/?f=json')
|
|
311
311
|
|
|
312
312
|
|
|
313
|
+
def test_update_settings(api, mock_raster_data):
|
|
314
|
+
raster1 = Raster(api, uuid=mock_raster_data['uuid'], data=mock_raster_data)
|
|
315
|
+
raster2 = Raster(api, uuid=mock_raster_data['uuid'], data=mock_raster_data)
|
|
316
|
+
settings = raster2.settings
|
|
317
|
+
api.put.return_value = settings
|
|
318
|
+
raster1.update_settings(raster2.settings)
|
|
319
|
+
api.put.assert_called_once_with(f'{raster1.endpoint}settings/', settings)
|
|
320
|
+
|
|
321
|
+
|
|
313
322
|
def test_set_settings(api, mock_raster_data):
|
|
314
323
|
"""Test setting raster settings."""
|
|
315
324
|
raster = Raster(api, uuid=mock_raster_data['uuid'], data=mock_raster_data)
|
|
@@ -625,6 +625,16 @@ def test_settings(api, mock_vector_data, layer_type):
|
|
|
625
625
|
assert settings == {'settings': 'value'}
|
|
626
626
|
|
|
627
627
|
|
|
628
|
+
@pytest.mark.parametrize("layer_type", [type for type in LayerType])
|
|
629
|
+
def test_update_settings(api, mock_vector_data, layer_type):
|
|
630
|
+
layer1 = VectorLayer(api, uuid=mock_vector_data['uuid'], data=mock_vector_data, layer_type=layer_type)
|
|
631
|
+
layer2 = VectorLayer(api, uuid=mock_vector_data['uuid'], data=mock_vector_data, layer_type=layer_type)
|
|
632
|
+
settings = layer2.settings
|
|
633
|
+
api.put.return_value = settings
|
|
634
|
+
layer1.update_settings(layer2.settings)
|
|
635
|
+
api.put.assert_called_once_with(f'{layer1.endpoint}settings/', settings)
|
|
636
|
+
|
|
637
|
+
|
|
628
638
|
@pytest.mark.parametrize("layer_type", [type for type in LayerType])
|
|
629
639
|
def test_set_settings(api, mock_vector_data, layer_type):
|
|
630
640
|
"""Test setting the settings of a layer."""
|
|
@@ -563,6 +563,16 @@ def test_settings(api, mock_view_data, layer_type):
|
|
|
563
563
|
assert settings == {'settings': 'value'}
|
|
564
564
|
|
|
565
565
|
|
|
566
|
+
@pytest.mark.parametrize("layer_type", [type for type in LayerType])
|
|
567
|
+
def test_update_settings(api, mock_view_data, layer_type):
|
|
568
|
+
view1 = VectorLayerView(api, uuid=mock_view_data['uuid'], data=mock_view_data, layer_type=layer_type)
|
|
569
|
+
view2 = VectorLayerView(api, uuid=mock_view_data['uuid'], data=mock_view_data, layer_type=layer_type)
|
|
570
|
+
settings = view2.settings
|
|
571
|
+
api.put.return_value = settings
|
|
572
|
+
view1.update_settings(view2.settings)
|
|
573
|
+
api.put.assert_called_once_with(f'{view1.endpoint}settings/', settings)
|
|
574
|
+
|
|
575
|
+
|
|
566
576
|
@pytest.mark.parametrize("layer_type", [type for type in LayerType])
|
|
567
577
|
def test_set_settings(api, mock_view_data, layer_type):
|
|
568
578
|
"""Test setting the settings of a layer."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|