geobox 1.3.1__tar.gz → 1.3.3__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.
Files changed (69) hide show
  1. {geobox-1.3.1 → geobox-1.3.3}/PKG-INFO +1 -1
  2. {geobox-1.3.1 → geobox-1.3.3}/geobox/api.py +6 -11
  3. {geobox-1.3.1 → geobox-1.3.3}/geobox/enums.py +0 -6
  4. {geobox-1.3.1 → geobox-1.3.3}/geobox/query.py +6 -8
  5. {geobox-1.3.1 → geobox-1.3.3}/geobox/raster.py +8 -2
  6. {geobox-1.3.1 → geobox-1.3.3}/geobox/tileset.py +66 -30
  7. {geobox-1.3.1 → geobox-1.3.3}/geobox/user.py +4 -7
  8. {geobox-1.3.1 → geobox-1.3.3}/geobox/utils.py +7 -0
  9. {geobox-1.3.1 → geobox-1.3.3}/geobox/vectorlayer.py +30 -0
  10. {geobox-1.3.1 → geobox-1.3.3}/geobox.egg-info/PKG-INFO +1 -1
  11. {geobox-1.3.1 → geobox-1.3.3}/pyproject.toml +1 -1
  12. {geobox-1.3.1 → geobox-1.3.3}/tests/test_tileset.py +43 -18
  13. {geobox-1.3.1 → geobox-1.3.3}/tests/test_user.py +4 -3
  14. {geobox-1.3.1 → geobox-1.3.3}/tests/test_vectorlayer.py +11 -0
  15. {geobox-1.3.1 → geobox-1.3.3}/LICENSE +0 -0
  16. {geobox-1.3.1 → geobox-1.3.3}/README.md +0 -0
  17. {geobox-1.3.1 → geobox-1.3.3}/geobox/__init__.py +0 -0
  18. {geobox-1.3.1 → geobox-1.3.3}/geobox/apikey.py +0 -0
  19. {geobox-1.3.1 → geobox-1.3.3}/geobox/attachment.py +0 -0
  20. {geobox-1.3.1 → geobox-1.3.3}/geobox/base.py +0 -0
  21. {geobox-1.3.1 → geobox-1.3.3}/geobox/basemap.py +0 -0
  22. {geobox-1.3.1 → geobox-1.3.3}/geobox/dashboard.py +0 -0
  23. {geobox-1.3.1 → geobox-1.3.3}/geobox/exception.py +0 -0
  24. {geobox-1.3.1 → geobox-1.3.3}/geobox/feature.py +0 -0
  25. {geobox-1.3.1 → geobox-1.3.3}/geobox/field.py +0 -0
  26. {geobox-1.3.1 → geobox-1.3.3}/geobox/file.py +0 -0
  27. {geobox-1.3.1 → geobox-1.3.3}/geobox/log.py +0 -0
  28. {geobox-1.3.1 → geobox-1.3.3}/geobox/map.py +0 -0
  29. {geobox-1.3.1 → geobox-1.3.3}/geobox/model3d.py +0 -0
  30. {geobox-1.3.1 → geobox-1.3.3}/geobox/mosaic.py +0 -0
  31. {geobox-1.3.1 → geobox-1.3.3}/geobox/plan.py +0 -0
  32. {geobox-1.3.1 → geobox-1.3.3}/geobox/route.py +0 -0
  33. {geobox-1.3.1 → geobox-1.3.3}/geobox/scene.py +0 -0
  34. {geobox-1.3.1 → geobox-1.3.3}/geobox/settings.py +0 -0
  35. {geobox-1.3.1 → geobox-1.3.3}/geobox/task.py +0 -0
  36. {geobox-1.3.1 → geobox-1.3.3}/geobox/tile3d.py +0 -0
  37. {geobox-1.3.1 → geobox-1.3.3}/geobox/usage.py +0 -0
  38. {geobox-1.3.1 → geobox-1.3.3}/geobox/version.py +0 -0
  39. {geobox-1.3.1 → geobox-1.3.3}/geobox/view.py +0 -0
  40. {geobox-1.3.1 → geobox-1.3.3}/geobox/workflow.py +0 -0
  41. {geobox-1.3.1 → geobox-1.3.3}/geobox.egg-info/SOURCES.txt +0 -0
  42. {geobox-1.3.1 → geobox-1.3.3}/geobox.egg-info/dependency_links.txt +0 -0
  43. {geobox-1.3.1 → geobox-1.3.3}/geobox.egg-info/requires.txt +0 -0
  44. {geobox-1.3.1 → geobox-1.3.3}/geobox.egg-info/top_level.txt +0 -0
  45. {geobox-1.3.1 → geobox-1.3.3}/setup.cfg +0 -0
  46. {geobox-1.3.1 → geobox-1.3.3}/tests/test_api.py +0 -0
  47. {geobox-1.3.1 → geobox-1.3.3}/tests/test_apikey.py +0 -0
  48. {geobox-1.3.1 → geobox-1.3.3}/tests/test_attachment.py +0 -0
  49. {geobox-1.3.1 → geobox-1.3.3}/tests/test_basemap.py +0 -0
  50. {geobox-1.3.1 → geobox-1.3.3}/tests/test_dashboard.py +0 -0
  51. {geobox-1.3.1 → geobox-1.3.3}/tests/test_feature.py +0 -0
  52. {geobox-1.3.1 → geobox-1.3.3}/tests/test_field.py +0 -0
  53. {geobox-1.3.1 → geobox-1.3.3}/tests/test_file.py +0 -0
  54. {geobox-1.3.1 → geobox-1.3.3}/tests/test_log.py +0 -0
  55. {geobox-1.3.1 → geobox-1.3.3}/tests/test_map.py +0 -0
  56. {geobox-1.3.1 → geobox-1.3.3}/tests/test_model3d.py +0 -0
  57. {geobox-1.3.1 → geobox-1.3.3}/tests/test_mosaic.py +0 -0
  58. {geobox-1.3.1 → geobox-1.3.3}/tests/test_plan.py +0 -0
  59. {geobox-1.3.1 → geobox-1.3.3}/tests/test_query.py +0 -0
  60. {geobox-1.3.1 → geobox-1.3.3}/tests/test_raster.py +0 -0
  61. {geobox-1.3.1 → geobox-1.3.3}/tests/test_route.py +0 -0
  62. {geobox-1.3.1 → geobox-1.3.3}/tests/test_scene.py +0 -0
  63. {geobox-1.3.1 → geobox-1.3.3}/tests/test_settings.py +0 -0
  64. {geobox-1.3.1 → geobox-1.3.3}/tests/test_task.py +0 -0
  65. {geobox-1.3.1 → geobox-1.3.3}/tests/test_tile3d.py +0 -0
  66. {geobox-1.3.1 → geobox-1.3.3}/tests/test_usage.py +0 -0
  67. {geobox-1.3.1 → geobox-1.3.3}/tests/test_version.py +0 -0
  68. {geobox-1.3.1 → geobox-1.3.3}/tests/test_view.py +0 -0
  69. {geobox-1.3.1 → geobox-1.3.3}/tests/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geobox
3
- Version: 1.3.1
3
+ Version: 1.3.3
4
4
  Summary: SDK for Geobox's APIs
5
5
  Author-email: Hamid Heydari <heydari.h62@gmail.com>
6
6
  License: MIT
@@ -781,16 +781,14 @@ class GeoboxClient:
781
781
  return VectorLayerView.get_view_by_name(self, name, user_id)
782
782
 
783
783
 
784
- def create_tileset(self, name: str, layers: List[Dict], display_name: str = None, description: str = None,
784
+ def create_tileset(self, name: str, layers: List[Union['VectorLayer', 'VectorLayerView']], display_name: str = None, description: str = None,
785
785
  min_zoom: int = None, max_zoom: int = None, user_id: int = None) -> 'Tileset':
786
786
  """
787
787
  Create a new tileset.
788
788
 
789
789
  Args:
790
790
  name (str): The name of the tileset.
791
- layers (List[Dict]): The layers of the tileset. a list of dictionaries with the following keys:
792
- - layer_type: The type of the layer. valid values are "vector" and "view".
793
- - layer_uuid: The uuid of the layer.
791
+ layers (List['VectorLayer' | 'VectorLayerView']): list of vectorlayer and view objects to add to tileset.
794
792
  display_name (str, optional): The display name of the tileset.
795
793
  description (str, optional): The description of the tileset.
796
794
  min_zoom (int, optional): The minimum zoom level of the tileset.
@@ -802,19 +800,16 @@ class GeoboxClient:
802
800
 
803
801
  Example:
804
802
  >>> from geobox import GeoboxClient
803
+ >>> from geobox.tileset import Tileset
805
804
  >>> client = GeoboxClient()
806
- >>> layers = [
807
- ... {
808
- ... "layer_type": "vector",
809
- ... "layer_uuid": "12345678-1234-5678-1234-567812345678"
810
- ... }
811
- ... ]
805
+ >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
806
+ >>> view = client.get_view(uuid="12345678-1234-5678-1234-567812345678")
812
807
  >>> tileset = client.create_tileset(name="your_tileset_name",
813
808
  ... display_name="Your Tileset",
814
809
  ... description="Your description",
815
810
  ... min_zoom=0,
816
811
  ... max_zoom=14,
817
- ... layers=layers)
812
+ ... layers=[layer, view])
818
813
  """
819
814
  return Tileset.create_tileset(api=self,
820
815
  name=name,
@@ -326,12 +326,6 @@ class QueryResultType(Enum):
326
326
  both = "both"
327
327
 
328
328
 
329
- class TilesetLayerType(Enum):
330
- Vector = "vector"
331
-
332
- View = "view"
333
-
334
-
335
329
  class FileOutputFormat(Enum):
336
330
  Shapefile = "Shapefile"
337
331
 
@@ -582,7 +582,7 @@ class Query(Base):
582
582
  super()._unshare(self.endpoint, users)
583
583
 
584
584
 
585
- def get_shared_users(self, search: str, skip: int = 0, limit: int = 10) -> List['User']:
585
+ def get_shared_users(self, search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
586
586
  """
587
587
  Retrieves the list of users the query is shared with.
588
588
 
@@ -624,18 +624,16 @@ class Query(Base):
624
624
  return endpoint
625
625
 
626
626
 
627
- def save_as_layer(self, layer_name: str, layer_type: 'QueryGeometryType') -> Dict:
627
+ def save_as_layer(self, layer_name: str, layer_type: 'QueryGeometryType' = None) -> Task:
628
628
  """
629
629
  Saves the query as a new layer.
630
630
 
631
631
  Args:
632
- sql (str): The SQL statement for the query.
633
- params (list): The parameters for the SQL statement.
634
632
  layer_name (str): The name of the new layer.
635
- layer_type (QueryGeometryType): The type of the new layer.
633
+ layer_type (QueryGeometryType, optional): The type of the new layer.
636
634
 
637
635
  Returns:
638
- Dict: The response from the API.
636
+ Task: The response task object.
639
637
 
640
638
  Raises:
641
639
  PermissionError: If the query is a read-only system query.
@@ -645,7 +643,7 @@ class Query(Base):
645
643
  >>> from geobox.query import Query
646
644
  >>> client = GeoboxClient()
647
645
  >>> query = Query.get_query(client, uuid="12345678-1234-5678-1234-567812345678")
648
- >>> query.save_as_layer(layer_name='test', layer_type=QueryGeometryType.POLYGON)
646
+ >>> query.save_as_layer(layer_name='test')
649
647
  """
650
648
  self._check_access()
651
649
 
@@ -659,7 +657,7 @@ class Query(Base):
659
657
  "sql": self.sql,
660
658
  "params": params,
661
659
  "layer_name": layer_name,
662
- "layer_type": layer_type.value
660
+ "layer_type": layer_type.value if layer_type else None
663
661
  })
664
662
 
665
663
  endpoint = urljoin(self.BASE_ENDPOINT, 'saveAsLayer/')
@@ -510,13 +510,13 @@ class Raster(Base):
510
510
  'colormap_name': kwargs.get('colormap_name'),
511
511
  'colormap': kwargs.get('colormap')
512
512
  })
513
- query_string = urlencode(clean_data(params))
513
+ query_string = urlencode(params)
514
514
  endpoint = urljoin(self.api.base_url, f'{self.endpoint}render/{z}/{x}/{y}.png')
515
515
  endpoint = urljoin(endpoint, f'?{query_string}')
516
516
  return endpoint
517
517
 
518
518
 
519
- def get_tile_pbf_url(self, x: int, y: int, z: int) -> str:
519
+ def get_tile_pbf_url(self, x: int, y: int, z: int, indexes: str = None) -> str:
520
520
  """
521
521
  Get the URL of the tile.
522
522
 
@@ -524,6 +524,7 @@ class Raster(Base):
524
524
  x (int): The x coordinate of the tile.
525
525
  y (int): The y coordinate of the tile.
526
526
  z (int): The zoom level of the tile.
527
+ indexes (str, optional): list of comma separated band indexes to be rendered. e.g. 1, 2, 3
527
528
 
528
529
  Returns:
529
530
  str: The URL of the tile.
@@ -535,7 +536,12 @@ class Raster(Base):
535
536
  >>> raster = Raster.get_raster(client, uuid="12345678-1234-5678-1234-567812345678")
536
537
  >>> raster.get_tile_pbf_url(x=10, y=20, z=1)
537
538
  """
539
+ params = clean_data({
540
+ 'indexes': indexes
541
+ })
542
+ query_string = urlencode(params)
538
543
  endpoint = urljoin(self.api.base_url, f'{self.endpoint}tiles/{z}/{x}/{y}.pbf')
544
+ endpoint = urljoin(endpoint, f'?{query_string}')
539
545
  return endpoint
540
546
 
541
547
 
@@ -3,8 +3,8 @@ from typing import Dict, List, Optional, Union, TYPE_CHECKING
3
3
 
4
4
  from .base import Base
5
5
  from .vectorlayer import VectorLayer
6
+ from .view import VectorLayerView
6
7
  from .task import Task
7
- from .enums import TilesetLayerType
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from . import GeoboxClient
@@ -35,7 +35,7 @@ class Tileset(Base):
35
35
 
36
36
 
37
37
  @classmethod
38
- def create_tileset(cls, api: 'GeoboxClient', name: str, layers: List[Dict], display_name: str = None, description: str = None,
38
+ def create_tileset(cls, api: 'GeoboxClient', name: str, layers: List[Union['VectorLayer', 'VectorLayerView']], display_name: str = None, description: str = None,
39
39
  min_zoom: int = None, max_zoom: int = None, user_id: int = None) -> 'Tileset':
40
40
  """
41
41
  Create a new tileset.
@@ -43,9 +43,7 @@ class Tileset(Base):
43
43
  Args:
44
44
  api (GeoboxClient): The GeoboxClient instance for making requests.
45
45
  name (str): The name of the tileset.
46
- layers (List[Dict]): The layers of the tileset. a list of dictionaries with the following keys:
47
- - layer_type: The type of the layer. valid values are "vector" and "view".
48
- - layer_uuid: The uuid of the layer.
46
+ layers (List['VectorLayer' | 'VectorLayerView']): list of vectorlayer and view objects to add to tileset.
49
47
  display_name (str, optional): The display name of the tileset.
50
48
  description (str, optional): The description of the tileset.
51
49
  min_zoom (int, optional): The minimum zoom level of the tileset.
@@ -59,34 +57,46 @@ class Tileset(Base):
59
57
  >>> from geobox import GeoboxClient
60
58
  >>> from geobox.tileset import Tileset
61
59
  >>> client = GeoboxClient()
62
- >>> layers = [
63
- ... {
64
- ... "layer_type": "vector",
65
- ... "layer_uuid": "12345678-1234-5678-1234-567812345678"
66
- ... }
67
- ... ]
60
+ >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
61
+ >>> view = client.get_view(uuid="12345678-1234-5678-1234-567812345678")
68
62
  >>> tileset = Tileset.create_tileset(client,
69
63
  ... name="your_tileset_name",
70
64
  ... display_name="Your Tileset",
71
65
  ... description="Your description",
72
66
  ... min_zoom=0,
73
67
  ... max_zoom=14,
74
- ... layers=layers)
68
+ ... layers=[layer, view])
75
69
  or
76
70
  >>> tileset = client.create_tileset(name="your_tileset_name",
77
71
  ... display_name="Your Tileset",
78
72
  ... description="Your description",
79
73
  ... min_zoom=0,
80
74
  ... max_zoom=14,
81
- ... layers=layers)
75
+ ... layers=[layer, view])
82
76
  """
77
+ formatted_layers = []
78
+ for item in layers:
79
+ if type(item) == VectorLayer:
80
+ item_type = 'vector'
81
+
82
+ elif type(item) == VectorLayerView:
83
+ item_type = 'view'
84
+
85
+ else:
86
+ continue
87
+
88
+ formatted_layers.append({
89
+ 'layer_type': item_type,
90
+ 'layer_uuid': item.uuid
91
+ })
92
+
83
93
  data = {
84
94
  "name": name,
85
95
  "display_name": display_name,
86
96
  "description": description,
87
97
  "min_zoom": min_zoom,
88
98
  "max_zoom": max_zoom,
89
- "layers": layers,
99
+ "layers": formatted_layers,
90
100
  "user_id": user_id
91
101
  }
92
102
  return super()._create(api, cls.BASE_ENDPOINT, data, factory_func=lambda api, item: Tileset(api, item['uuid'], item))
@@ -283,7 +293,7 @@ class Tileset(Base):
283
293
  super().delete(urljoin(self.BASE_ENDPOINT, f'{self.uuid}/'))
284
294
 
285
295
 
286
- def get_tileset_layers(self, **kwargs) -> List['VectorLayer']:
296
+ def get_layers(self, **kwargs) -> List['VectorLayer']:
287
297
  """
288
298
  Retrieves the layers of the tileset with optional parameters.
289
299
 
@@ -307,13 +317,14 @@ class Tileset(Base):
307
317
  Example:
308
318
 
309
319
  Returns:
310
- List: A list of VectorLayer instances.
320
+ List: A list of VectorLayer or VectorLayerView instances.
311
321
 
312
322
  Example:
313
323
  >>> from geobox import GeoboxClient
314
324
  >>> from geobox.tileset import Tileset
315
325
  >>> client = GeoboxClient()
316
- >>> tilesets = Tileset.get_tileset_layers()
326
+ >>> tileset = Tileset.get_tileset(client, uuid="12345678-1234-5678-1234-567812345678")
327
+ >>> layers = tileset.get_layers()
317
328
  """
318
329
  params = {
319
330
  'f': 'json',
@@ -328,57 +339,82 @@ class Tileset(Base):
328
339
  'shared': kwargs.get('shared', False)
329
340
  }
330
341
  endpoint = urljoin(self.BASE_ENDPOINT, f'{self.uuid}/layers/')
331
- return super()._get_list(self.api, endpoint, params, factory_func=lambda api, item: VectorLayer(api, item['uuid'], item['layer_type'], item))
342
+ return super()._get_list(self.api, endpoint, params, factory_func=lambda api, item: VectorLayer(api, item['uuid'], item['layer_type'], item) if not item['is_view'] else \
343
+ VectorLayerView(api, item['uuid'], item['layer_type'], item) )
332
344
 
333
345
 
334
- def add_layer(self, layer_type: TilesetLayerType, layer_uuid: str) -> None:
346
+ def add_layer(self, layer: Union['VectorLayer', 'VectorLayerView']) -> None:
335
347
  """
336
348
  Adds a layer to the tileset.
337
349
 
338
350
  Args:
339
- layer_type (TilesetLayerType): The type of the layer. "vector" or "view".
340
- layer_uuid (str): The UUID of the layer.
351
+ layer (VectorLayer | VectorLayerView): the layer object to add to the tileset
341
352
 
342
353
  Returns:
343
354
  None
344
355
 
356
+ Raises:
357
+ ValueError: if the layer input is not a 'VectorLayer' or 'VetorLayerview' object
358
+
345
359
  Example:
346
360
  >>> from geobox import GeoboxClient
347
361
  >>> from geobox.tileset import Tileset
348
362
  >>> client = GeoboxClient()
349
363
  >>> tileset = Tileset.get_tileset(client, uuid="12345678-1234-5678-1234-567812345678")
350
- >>> tileset.add_layer_tileset(layer_type=TilesetLayerType.Vector, layer_uuid="12345678-1234-5678-1234-567812345678")
364
+ >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
365
+ >>> tileset.add_layer(layer)
351
366
  """
367
+ if type(layer) == VectorLayer:
368
+ layer_type = 'vector'
369
+
370
+ elif type(layer) == VectorLayerView:
371
+ layer_type = 'view'
372
+
373
+ else:
374
+ raise ValueError("layer input must be either 'VectorLayer' or 'VetorLayerview' object")
375
+
352
376
  data = {
353
- "layer_type": layer_type.value,
354
- "layer_uuid": layer_uuid
377
+ "layer_type": layer_type,
378
+ "layer_uuid": layer.uuid
355
379
  }
356
380
 
357
381
  endpoint = urljoin(self.endpoint, 'layers/')
358
382
  return self.api.post(endpoint, data, is_json=False)
359
383
 
360
384
 
361
- def delete_layer(self, layer_type: TilesetLayerType, layer_uuid: str) -> None:
385
+ def delete_layer(self, layer: Union['VectorLayer', 'VectorLayerView']) -> None:
362
386
  """
363
387
  Deletes a layer from the tileset.
364
388
 
365
389
  Args:
366
- layer_type (TilesetLayerType): The type of the layer. "vector" or "view".
367
- layer_uuid (str): The UUID of the layer.
390
+ layer (VectorLayer | VectorLayerView): the layer object to delete from the tileset
368
391
 
369
392
  Returns:
370
393
  None
371
394
 
395
+ Raises:
396
+ ValueError: if the layer input is not a 'VectorLayer' or 'VetorLayerview' object
397
+
372
398
  Example:
373
399
  >>> from geobox import GeoboxClient
374
400
  >>> from geobox.tileset import Tileset
375
401
  >>> client = GeoboxClient()
376
402
  >>> tileset = Tileset.get_tileset(client, uuid="12345678-1234-5678-1234-567812345678")
377
- >>> tileset.delete_layer_tileset(layer_type=TilesetLayerType.Vector, layer_uuid="12345678-1234-5678-1234-567812345678")
403
+ >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
404
+ >>> tileset.delete_layer(layer)
378
405
  """
406
+ if type(layer) == VectorLayer:
407
+ layer_type = 'vector'
408
+
409
+ elif type(layer) == VectorLayerView:
410
+ layer_type = 'view'
411
+
412
+ else:
413
+ raise ValueError("layer input must be either 'VectorLayer' or 'VetorLayerview' object")
414
+
379
415
  data = {
380
- "layer_type": layer_type.value,
381
- "layer_uuid": layer_uuid
416
+ "layer_type": layer_type,
417
+ "layer_uuid": layer.uuid
382
418
  }
383
419
 
384
420
  endpoint = urljoin(self.endpoint, 'layers/')
@@ -2,7 +2,7 @@ from typing import List, Any, TYPE_CHECKING, Union, Dict
2
2
  from urllib.parse import urlencode, urljoin
3
3
 
4
4
  from .base import Base
5
- from .utils import clean_data
5
+ from .utils import clean_data, xor_encode
6
6
  from .enums import UserRole, UserStatus
7
7
  from .plan import Plan
8
8
 
@@ -113,11 +113,9 @@ class User(Base):
113
113
  'return_count': kwargs.get('return_count', False),
114
114
  'skip': kwargs.get('skip', 0),
115
115
  'limit': kwargs.get('limit', 10),
116
- 'user_id': kwargs.get('user_id'),
117
- 'shared': kwargs.get('shared', False)
116
+ 'user_id': kwargs.get('user_id')
118
117
  }
119
118
  return super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: User(api, item['id'], item))
120
-
121
119
 
122
120
 
123
121
  @classmethod
@@ -174,7 +172,7 @@ class User(Base):
174
172
  data = {
175
173
  "username": username,
176
174
  "email": email,
177
- "password": password,
175
+ "password": xor_encode(password),
178
176
  "role": role.value,
179
177
  "first_name": first_name,
180
178
  "last_name": last_name,
@@ -184,7 +182,6 @@ class User(Base):
184
182
  return super()._create(api, cls.BASE_ENDPOINT, data, factory_func=lambda api, item: User(api, item['id'], item))
185
183
 
186
184
 
187
-
188
185
  @classmethod
189
186
  def search_users(cls, api: 'GeoboxClient', search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
190
187
  """
@@ -377,7 +374,7 @@ class User(Base):
377
374
  >>> user.change_password(new_password='user_new_password')
378
375
  """
379
376
  data = clean_data({
380
- "new_password": new_password
377
+ "new_password": xor_encode(new_password)
381
378
  })
382
379
  endpoint = urljoin(self.endpoint, 'change-password')
383
380
  self.api.post(endpoint, data, is_json=False)
@@ -1,4 +1,11 @@
1
1
  from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
2
+ import base64
3
+
4
+
5
+ def xor_encode(s, key=42):
6
+ xor_str = ''.join(chr(ord(c) ^ key) for c in s)
7
+ encoded_bytes = base64.b64encode(xor_str.encode('utf-8'))
8
+ return encoded_bytes.decode('utf-8')
2
9
 
3
10
 
4
11
  def clean_data(data: dict) -> dict:
@@ -439,6 +439,36 @@ class VectorLayer(Base):
439
439
  return super()._create(self.api, endpoint, data, factory_func=lambda api, item: VectorLayerVersion(api, item['uuid'], item))
440
440
 
441
441
 
442
+ def get_versions(self, **kwargs) -> List['VectorLayerVersion']:
443
+ """
444
+ Get list of versions of the current vector layer object with optional filtering and pagination.
445
+
446
+ Keyword Args:
447
+ q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
448
+ 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.
449
+ search_fields (str): comma separated list of fields for searching.
450
+ 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.
451
+ return_count (bool): Whether to return total count. default is False.
452
+ skip (int): Number of items to skip. default is 0.
453
+ limit (int): Number of items to return. default is 10.
454
+ user_id (int): Specific user. privileges required.
455
+ shared (bool): Whether to return shared versions. default is False.
456
+
457
+ Returns:
458
+ List[VectorLayerVersion] | int: A list of vector layer version instances or the total number of versions.
459
+
460
+ Example:
461
+ >>> from geobox import GeoboxClient
462
+ >>> from geobox.version import VectorLayerVersion
463
+ >>> client = GeoboxClient()
464
+ >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
465
+ >>> versions = layer.get_versions()
466
+ or
467
+ >>> versions = layer.get_versions()
468
+ """
469
+ return VectorLayerVersion.get_versions(self.api, layer_id=self.id, **kwargs)
470
+
471
+
442
472
  @property
443
473
  def wfs(self) -> str:
444
474
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geobox
3
- Version: 1.3.1
3
+ Version: 1.3.3
4
4
  Summary: SDK for Geobox's APIs
5
5
  Author-email: Hamid Heydari <heydari.h62@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "geobox"
7
- version = "1.3.1"
7
+ version = "1.3.3"
8
8
  description = "SDK for Geobox's APIs"
9
9
  authors = [
10
10
  {name = "Hamid Heydari", email = "heydari.h62@gmail.com"}
@@ -2,10 +2,12 @@ import pytest
2
2
  from urllib.parse import urljoin
3
3
  from unittest.mock import patch
4
4
 
5
- from geobox.tileset import Tileset, TilesetLayerType
5
+ from geobox.tileset import Tileset
6
6
  from geobox.enums import LayerType
7
7
  from geobox.user import User
8
8
  from geobox.task import Task
9
+ from geobox.vectorlayer import VectorLayer
10
+ from geobox.view import VectorLayerView
9
11
 
10
12
 
11
13
  def test_init(api, mock_tileset_data):
@@ -35,15 +37,11 @@ def test_getattr(api, mock_tileset_data):
35
37
  _ = tileset.nonexistent_attribute
36
38
 
37
39
 
38
- def test_create_tileset(api, mock_tileset_data):
40
+ def test_create_tileset(api, mock_tileset_data, mock_vector_data, mock_view_data):
39
41
  """Test creating a tileset"""
40
42
  api.post.return_value = mock_tileset_data
41
- layers = [
42
- {
43
- "layer_type": "vector",
44
- "layer_uuid": "your_layer_uuid"
45
- }
46
- ]
43
+ layers = [VectorLayer(api, uuid=mock_vector_data['uuid'], layer_type=LayerType.MultiPoint, data=mock_vector_data),
44
+ VectorLayerView(api, uuid=mock_view_data['uuid'], layer_type=LayerType.MultiPoint, data=mock_view_data)]
47
45
  tileset = Tileset.create_tileset(api,
48
46
  mock_tileset_data['name'],
49
47
  layers=layers,
@@ -54,6 +52,13 @@ def test_create_tileset(api, mock_tileset_data):
54
52
  assert isinstance(tileset, Tileset)
55
53
  assert tileset.uuid == mock_tileset_data['uuid']
56
54
  assert tileset.data == mock_tileset_data
55
+ api.post.assert_called_once_with('tilesets/', {'name': mock_tileset_data['name'],
56
+ 'min_zoom': mock_tileset_data['min_zoom'],
57
+ 'max_zoom': mock_tileset_data['max_zoom'],
58
+ 'layers': [{'layer_type': 'vector',
59
+ 'layer_uuid': mock_vector_data['uuid']},
60
+ {'layer_type': 'view',
61
+ 'layer_uuid': mock_view_data['uuid']}]})
57
62
 
58
63
 
59
64
  def test_get_tilesets(api, mock_tileset_data):
@@ -133,43 +138,63 @@ def test_delete(api, mock_tileset_data):
133
138
  api.delete.assert_called_once_with(endpoint)
134
139
 
135
140
 
136
- def test_get_tileset_layers(api, mock_tileset_data, mock_vector_data):
141
+ def test_get_layers(api, mock_tileset_data, mock_vector_data, mock_view_data):
137
142
  """Test getting tileset layers."""
138
143
  mock_layers = [
139
144
  mock_vector_data,
140
- mock_vector_data
145
+ mock_view_data
141
146
  ]
142
147
  with patch.object(api, 'get', return_value=mock_layers):
143
148
  tileset = Tileset(api, uuid=mock_tileset_data['uuid'], data=mock_tileset_data)
144
- layers = tileset.get_tileset_layers()
149
+ layers = tileset.get_layers()
145
150
  assert len(layers) == 2
146
- assert all(layer.layer_type == LayerType.Polygon for layer in layers)
147
151
  assert layers[0].name == mock_vector_data['name']
148
- assert layers[1].name == mock_vector_data['name']
152
+ assert type(layers[0]) == VectorLayer
153
+ assert layers[1].name == mock_view_data['name']
154
+ assert type(layers[1]) == VectorLayerView
155
+
149
156
  # return count
150
157
  with patch.object(api, 'get', return_value=10):
151
158
  tileset = Tileset(api, uuid=mock_tileset_data['uuid'], data=mock_tileset_data)
152
- layer_count = tileset.get_tileset_layers(return_count=True)
159
+ layer_count = tileset.get_layers(return_count=True)
153
160
  assert layer_count == 10
154
161
  api.get.assert_called_once_with(f'{tileset.endpoint}layers/?f=json&return_count=True&skip=0&limit=10&shared=False')
155
162
 
156
163
 
157
- def test_add_layer(api, mock_tileset_data, mock_vector_data):
164
+ def test_add_layer(api, mock_tileset_data, mock_vector_data, mock_view_data):
158
165
  """Test adding layer to tileset."""
159
166
  with patch.object(api, 'post'):
160
167
  tileset = Tileset(api, uuid=mock_tileset_data['uuid'], data=mock_tileset_data)
161
- tileset.add_layer(layer_type=TilesetLayerType.Vector, layer_uuid=mock_vector_data['uuid'])
168
+ layer = VectorLayer(api, uuid=mock_vector_data['uuid'], layer_type=LayerType.MultiPoint, data=mock_vector_data)
169
+ view = VectorLayerView(api, uuid=mock_view_data['uuid'], layer_type=LayerType.MultiPoint, data=mock_view_data)
170
+ tileset.add_layer(layer=layer)
162
171
  endpoint = urljoin(tileset.endpoint, 'layers/')
163
172
  api.post.assert_called_once_with(endpoint, {'layer_type': 'vector', 'layer_uuid': mock_vector_data['uuid']}, is_json=False)
173
+ api.post.reset_mock()
174
+ tileset.add_layer(layer=view)
175
+ api.post.assert_called_once_with(endpoint, {'layer_type': 'view', 'layer_uuid': mock_view_data['uuid']}, is_json=False)
176
+
177
+ # usupported input
178
+ with pytest.raises(ValueError):
179
+ tileset.add_layer(layer='')
164
180
 
165
181
 
166
- def test_delete_layer(api, mock_tileset_data, mock_vector_data):
182
+ def test_delete_layer(api, mock_tileset_data, mock_vector_data, mock_view_data):
167
183
  """Test deleting layer from tileset."""
168
184
  with patch.object(api, 'delete'):
169
185
  tileset = Tileset(api, uuid=mock_tileset_data['uuid'], data=mock_tileset_data)
170
- tileset.delete_layer(layer_type=TilesetLayerType.Vector, layer_uuid=mock_vector_data['uuid'])
186
+ layer = VectorLayer(api, uuid=mock_vector_data['uuid'], layer_type=LayerType.MultiPoint, data=mock_vector_data)
187
+ view = VectorLayerView(api, uuid=mock_view_data['uuid'], layer_type=LayerType.MultiPoint, data=mock_view_data)
188
+ tileset.delete_layer(layer=layer)
171
189
  endpoint = urljoin(tileset.endpoint, 'layers/')
172
190
  api.delete.assert_called_once_with(endpoint, {'layer_type': 'vector', 'layer_uuid': mock_vector_data['uuid']}, is_json=False)
191
+ api.delete.reset_mock()
192
+ tileset.delete_layer(layer=view)
193
+ api.delete.assert_called_once_with(endpoint, {'layer_type': 'view', 'layer_uuid': mock_view_data['uuid']}, is_json=False)
194
+
195
+ # usupported input
196
+ with pytest.raises(ValueError):
197
+ tileset.delete_layer(layer='')
173
198
 
174
199
 
175
200
  def test_share(api, mock_tileset_data, mock_user_data):
@@ -5,6 +5,7 @@ from urllib.parse import urljoin, urlencode
5
5
  from geobox.user import User
6
6
  from geobox.enums import UserRole, UserStatus
7
7
  from geobox.plan import Plan
8
+ from geobox.utils import xor_encode
8
9
 
9
10
 
10
11
  def test_init(api, mock_user_data):
@@ -38,7 +39,7 @@ def test_get_users(api, mock_admin_user_data):
38
39
  result = User.get_users(api, search='test')
39
40
  assert isinstance(result, list)
40
41
  assert isinstance(result[0], User)
41
- api.get.assert_called_once_with('users/?f=json&search=test&return_count=False&skip=0&limit=10&shared=False')
42
+ api.get.assert_called_once_with('users/?f=json&search=test&return_count=False&skip=0&limit=10')
42
43
 
43
44
  def test_create_user(api, mock_admin_user_data):
44
45
  api.post.return_value = mock_admin_user_data
@@ -57,7 +58,7 @@ def test_create_user(api, mock_admin_user_data):
57
58
  expected_data = {
58
59
  "username": mock_admin_user_data['username'],
59
60
  "email": mock_admin_user_data['email'],
60
- "password": 'password',
61
+ "password": xor_encode('password'),
61
62
  "role": UserRole.ACCOUNT_ADMIN.value,
62
63
  "first_name": mock_admin_user_data['first_name'],
63
64
  "last_name": mock_admin_user_data['last_name'],
@@ -126,7 +127,7 @@ def test_change_password(api, mock_admin_user_data):
126
127
  with patch.object(api, 'post', return_value={"message": "success"}):
127
128
  user.change_password('new_password')
128
129
  expected_endpoint = urljoin(user.endpoint, 'change-password')
129
- api.post.assert_called_once_with(expected_endpoint, {'new_password': 'new_password'}, is_json=False)
130
+ api.post.assert_called_once_with(expected_endpoint, {'new_password': xor_encode('new_password')}, is_json=False)
130
131
 
131
132
 
132
133
  def test_renew_plan(api, mock_admin_user_data):
@@ -253,6 +253,17 @@ def test_create_version(api, mock_vector_data, layer_type, mock_version_data):
253
253
  api.post.assert_called_once_with(f'{layer.endpoint}versions', {'name': 'design_version', 'display_name': 'Design Version', 'description': 'This layer represents design version.'})
254
254
 
255
255
 
256
+ @pytest.mark.parametrize("layer_type", [type for type in LayerType])
257
+ def test_get_versions(api, mock_vector_data, layer_type, mock_version_data):
258
+ layer = VectorLayer(api, uuid=mock_vector_data['uuid'], data=mock_vector_data, layer_type=layer_type)
259
+ api.get.return_value = [mock_version_data]
260
+ versions = layer.get_versions()
261
+ assert len(versions) == 1
262
+ assert versions[0].uuid == mock_version_data['uuid']
263
+ assert versions[0].data == mock_version_data
264
+ api.get.assert_called_once_with(f'vectorLayerVersions/?layer_id={layer.id}&f=json&return_count=False&skip=0&limit=10&shared=False')
265
+
266
+
256
267
  @pytest.mark.parametrize("layer_type", [type for type in LayerType])
257
268
  def test_wfs_access_token(api, mock_vector_data, layer_type):
258
269
  layer = VectorLayer(api, uuid=mock_vector_data['uuid'], data=mock_vector_data, layer_type=layer_type)
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