geobox 1.2.2__tar.gz → 1.3.0__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.2.2 → geobox-1.3.0}/PKG-INFO +1 -1
  2. {geobox-1.2.2 → geobox-1.3.0}/geobox/api.py +6 -4
  3. {geobox-1.2.2 → geobox-1.3.0}/geobox/enums.py +9 -21
  4. {geobox-1.2.2 → geobox-1.3.0}/geobox/field.py +2 -2
  5. {geobox-1.2.2 → geobox-1.3.0}/geobox/map.py +14 -13
  6. {geobox-1.2.2 → geobox-1.3.0}/geobox/raster.py +2 -5
  7. {geobox-1.2.2 → geobox-1.3.0}/geobox/task.py +14 -7
  8. {geobox-1.2.2 → geobox-1.3.0}/geobox/user.py +32 -5
  9. {geobox-1.2.2 → geobox-1.3.0}/geobox/vectorlayer.py +99 -53
  10. {geobox-1.2.2 → geobox-1.3.0}/geobox/view.py +38 -33
  11. {geobox-1.2.2 → geobox-1.3.0}/geobox.egg-info/PKG-INFO +1 -1
  12. {geobox-1.2.2 → geobox-1.3.0}/pyproject.toml +1 -1
  13. {geobox-1.2.2 → geobox-1.3.0}/tests/test_feature.py +101 -102
  14. {geobox-1.2.2 → geobox-1.3.0}/tests/test_map.py +24 -92
  15. {geobox-1.2.2 → geobox-1.3.0}/tests/test_mosaic.py +49 -17
  16. {geobox-1.2.2 → geobox-1.3.0}/tests/test_raster.py +52 -16
  17. {geobox-1.2.2 → geobox-1.3.0}/tests/test_task.py +27 -18
  18. {geobox-1.2.2 → geobox-1.3.0}/tests/test_user.py +5 -0
  19. {geobox-1.2.2 → geobox-1.3.0}/tests/test_vectorlayer.py +166 -101
  20. {geobox-1.2.2 → geobox-1.3.0}/tests/test_view.py +92 -110
  21. {geobox-1.2.2 → geobox-1.3.0}/LICENSE +0 -0
  22. {geobox-1.2.2 → geobox-1.3.0}/README.md +0 -0
  23. {geobox-1.2.2 → geobox-1.3.0}/geobox/__init__.py +0 -0
  24. {geobox-1.2.2 → geobox-1.3.0}/geobox/apikey.py +0 -0
  25. {geobox-1.2.2 → geobox-1.3.0}/geobox/attachment.py +0 -0
  26. {geobox-1.2.2 → geobox-1.3.0}/geobox/base.py +0 -0
  27. {geobox-1.2.2 → geobox-1.3.0}/geobox/basemap.py +0 -0
  28. {geobox-1.2.2 → geobox-1.3.0}/geobox/dashboard.py +0 -0
  29. {geobox-1.2.2 → geobox-1.3.0}/geobox/exception.py +0 -0
  30. {geobox-1.2.2 → geobox-1.3.0}/geobox/feature.py +0 -0
  31. {geobox-1.2.2 → geobox-1.3.0}/geobox/file.py +0 -0
  32. {geobox-1.2.2 → geobox-1.3.0}/geobox/log.py +0 -0
  33. {geobox-1.2.2 → geobox-1.3.0}/geobox/model3d.py +0 -0
  34. {geobox-1.2.2 → geobox-1.3.0}/geobox/mosaic.py +0 -0
  35. {geobox-1.2.2 → geobox-1.3.0}/geobox/plan.py +0 -0
  36. {geobox-1.2.2 → geobox-1.3.0}/geobox/query.py +0 -0
  37. {geobox-1.2.2 → geobox-1.3.0}/geobox/route.py +0 -0
  38. {geobox-1.2.2 → geobox-1.3.0}/geobox/scene.py +0 -0
  39. {geobox-1.2.2 → geobox-1.3.0}/geobox/settings.py +0 -0
  40. {geobox-1.2.2 → geobox-1.3.0}/geobox/tile3d.py +0 -0
  41. {geobox-1.2.2 → geobox-1.3.0}/geobox/tileset.py +0 -0
  42. {geobox-1.2.2 → geobox-1.3.0}/geobox/usage.py +0 -0
  43. {geobox-1.2.2 → geobox-1.3.0}/geobox/utils.py +0 -0
  44. {geobox-1.2.2 → geobox-1.3.0}/geobox/version.py +0 -0
  45. {geobox-1.2.2 → geobox-1.3.0}/geobox/workflow.py +0 -0
  46. {geobox-1.2.2 → geobox-1.3.0}/geobox.egg-info/SOURCES.txt +0 -0
  47. {geobox-1.2.2 → geobox-1.3.0}/geobox.egg-info/dependency_links.txt +0 -0
  48. {geobox-1.2.2 → geobox-1.3.0}/geobox.egg-info/requires.txt +0 -0
  49. {geobox-1.2.2 → geobox-1.3.0}/geobox.egg-info/top_level.txt +0 -0
  50. {geobox-1.2.2 → geobox-1.3.0}/setup.cfg +0 -0
  51. {geobox-1.2.2 → geobox-1.3.0}/tests/test_api.py +0 -0
  52. {geobox-1.2.2 → geobox-1.3.0}/tests/test_apikey.py +0 -0
  53. {geobox-1.2.2 → geobox-1.3.0}/tests/test_attachment.py +0 -0
  54. {geobox-1.2.2 → geobox-1.3.0}/tests/test_basemap.py +0 -0
  55. {geobox-1.2.2 → geobox-1.3.0}/tests/test_dashboard.py +0 -0
  56. {geobox-1.2.2 → geobox-1.3.0}/tests/test_field.py +0 -0
  57. {geobox-1.2.2 → geobox-1.3.0}/tests/test_file.py +0 -0
  58. {geobox-1.2.2 → geobox-1.3.0}/tests/test_log.py +0 -0
  59. {geobox-1.2.2 → geobox-1.3.0}/tests/test_model3d.py +0 -0
  60. {geobox-1.2.2 → geobox-1.3.0}/tests/test_plan.py +0 -0
  61. {geobox-1.2.2 → geobox-1.3.0}/tests/test_query.py +0 -0
  62. {geobox-1.2.2 → geobox-1.3.0}/tests/test_route.py +0 -0
  63. {geobox-1.2.2 → geobox-1.3.0}/tests/test_scene.py +0 -0
  64. {geobox-1.2.2 → geobox-1.3.0}/tests/test_settings.py +0 -0
  65. {geobox-1.2.2 → geobox-1.3.0}/tests/test_tile3d.py +0 -0
  66. {geobox-1.2.2 → geobox-1.3.0}/tests/test_tileset.py +0 -0
  67. {geobox-1.2.2 → geobox-1.3.0}/tests/test_usage.py +0 -0
  68. {geobox-1.2.2 → geobox-1.3.0}/tests/test_version.py +0 -0
  69. {geobox-1.2.2 → geobox-1.3.0}/tests/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geobox
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: SDK for Geobox's APIs
5
5
  Author-email: Hamid Heydari <heydari.h62@gmail.com>
6
6
  License: MIT
@@ -31,7 +31,7 @@ from .basemap import Basemap
31
31
  from .attachment import Attachment, AttachmentResourceType
32
32
  from .apikey import ApiKey
33
33
  from .log import Log
34
- from .usage import Usage
34
+ from .usage import Usage, UsageScale, UsageParam
35
35
 
36
36
  logger = logging.getLogger(__name__)
37
37
 
@@ -500,6 +500,7 @@ class GeoboxClient:
500
500
  display_name: str = None,
501
501
  description: str = None,
502
502
  has_z: bool = False,
503
+ temporary: bool = False,
503
504
  fields: List = None) -> 'VectorLayer':
504
505
  """
505
506
  Create a new vector layer.
@@ -510,6 +511,7 @@ class GeoboxClient:
510
511
  display_name (str, optional): A human-readable name for the layer. default is None.
511
512
  description (str, optional): A description of the layer. default is None.
512
513
  has_z (bool, optional): Whether the layer includes Z coordinates. default is False.
514
+ temporary (bool, optional): Whether to create a temporary layer. temporary layers will be deleted after 24 hours. default is False.
513
515
  fields (List, optional): List of field definitions for the layer. default is None.
514
516
 
515
517
  Returns:
@@ -528,7 +530,7 @@ class GeoboxClient:
528
530
  ... has_z=False,
529
531
  ... fields=[{"name": "my_field", "datatype": "FieldTypeString"}])
530
532
  """
531
- return VectorLayer.create_vector(self, name=name, layer_type=layer_type, display_name=display_name, description=description, has_z=has_z, fields=fields)
533
+ return VectorLayer.create_vector(self, name=name, layer_type=layer_type, display_name=display_name, description=description, has_z=has_z, temporary=temporary, fields=fields)
532
534
 
533
535
 
534
536
  def get_vector_by_name(self, name: str, user_id: int = None) -> Union['VectorLayer', None]:
@@ -760,7 +762,7 @@ class GeoboxClient:
760
762
  return VectorLayerView.get_view(self, uuid)
761
763
 
762
764
 
763
- def get_view_by_name(self, name: str, user_id: int = None) -> Union['View', None]:
765
+ def get_view_by_name(self, name: str, user_id: int = None) -> Union['VectorLayerView', None]:
764
766
  """
765
767
  Get a view by name
766
768
 
@@ -769,7 +771,7 @@ class GeoboxClient:
769
771
  user_id (int, optional): specific user. privileges required.
770
772
 
771
773
  Returns:
772
- View | None: returns the view if a view matches the given name, else None
774
+ VectorLayerView | None: returns the view if a view matches the given name, else None
773
775
 
774
776
  Example:
775
777
  >>> from geobox import GeoboxClient
@@ -134,20 +134,11 @@ class LayerType(Enum):
134
134
  Point = "Point"
135
135
  """A single point in 2D space"""
136
136
 
137
- LineString = "LineString"
138
- """A sequence of points forming a line"""
139
-
140
- Polygon = "Polygon"
141
- """A closed shape defined by a sequence of points"""
142
-
143
137
  MultiPoint = "MultiPoint"
144
138
  """A collection of points"""
145
139
 
146
- MultiLineString = "MultiLineString"
147
- """A collection of lines"""
148
-
149
- MultiPolygon = "MultiPolygon"
150
- """A collection of polygons"""
140
+ Polygon = "Polygon"
141
+ """A closed shape defined by a sequence of points"""
151
142
 
152
143
  Polyline = "Polyline"
153
144
  """A polyline geometry"""
@@ -163,25 +154,22 @@ class FeatureType(Enum):
163
154
  """
164
155
  Point = "Point"
165
156
  """A single point in 2D space"""
157
+
158
+ MultiPoint = "MultiPoint"
159
+ """A collection of points"""
166
160
 
167
161
  LineString = "LineString"
168
162
  """A sequence of points forming a line"""
163
+
164
+ MultiLineString = "MultiLineString"
165
+ """A collection of lines"""
169
166
 
170
167
  Polygon = "Polygon"
171
168
  """A closed shape defined by a sequence of points"""
172
169
 
173
- MultiPoint = "MultiPoint"
174
- """A collection of points"""
175
-
176
- MultiLineString = "MultiLineString"
177
- """A collection of lines"""
178
-
179
170
  MultiPolygon = "MultiPolygon"
180
171
  """A collection of polygons"""
181
172
 
182
- Polyline = "Polyline"
183
- """A polyline geometry"""
184
-
185
173
 
186
174
  # Task Enums
187
175
  class TaskStatus(Enum):
@@ -269,7 +257,7 @@ class UserRole(Enum):
269
257
 
270
258
  EDITOR = "Editor"
271
259
 
272
- VIEWR = "Viewer"
260
+ VIEWER = "Viewer"
273
261
 
274
262
 
275
263
  class UserStatus(Enum):
@@ -258,7 +258,7 @@ class Field(Base):
258
258
  Get the statistic of the field.
259
259
 
260
260
  Args:
261
- func (str): The function to apply to the field.
261
+ func (str): The function to apply to the field. values are: min, max, avg
262
262
 
263
263
  Returns:
264
264
  Dict: The response data.
@@ -269,7 +269,7 @@ class Field(Base):
269
269
  >>> client = GeoboxClient()
270
270
  >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
271
271
  >>> field = layer.get_field(name='test')
272
- >>> field.get_field_statistic()
272
+ >>> field.get_field_statistic(func='avg')
273
273
  """
274
274
  endpoint = urljoin(self.endpoint, f'stats/?func_type={func}')
275
275
  return self.layer.api.get(endpoint)
@@ -621,31 +621,32 @@ class Map(Base):
621
621
  grid_settings = {'enable_grid', 'grid_unit', 'grid_width', 'grid_height', 'grid_minzoom', 'grid_maxzoom'}
622
622
  view_settings = {'bearing', 'pitch', 'center', 'zoom'}
623
623
 
624
+ settings = self.settings
625
+
624
626
  for key, value in kwargs.items():
625
627
  if key in general_settings:
626
- self.settings['general_settings'][key] = value
628
+ settings['general_settings'][key] = value
627
629
  elif key in edit_settings:
628
- self.settings['edit_settings'][key] = value
630
+ settings['edit_settings'][key] = value
629
631
  elif key in snap_settings:
630
- self.settings['snap_settings'][key] = value
632
+ settings['snap_settings'][key] = value
631
633
  elif key == 'controls':
632
- self.settings['controls'] = value
634
+ settings['controls'] = value
633
635
  elif key in search_settings:
634
- self.settings['search_settings'][key] = value
636
+ settings['search_settings'][key] = value
635
637
  elif key in marker_settings:
636
- self.settings['marker_settings'][key] = value
638
+ settings['marker_settings'][key] = value
637
639
  elif key in terrain_settings:
638
- self.settings['terrain_settings'][key] = value
640
+ settings['terrain_settings'][key] = value
639
641
  elif key in grid_settings:
640
- self.settings['grid_settings'][key] = value
642
+ settings['grid_settings'][key] = value
641
643
  elif key in view_settings:
642
- self.settings['view_settings'][key] = value
644
+ settings['view_settings'][key] = value
643
645
  elif key == 'toc_settings':
644
- self.settings['toc_settings'] = value
646
+ settings['toc_settings'] = value
647
+
648
+ return super()._set_settings(self.endpoint, settings)
645
649
 
646
- endpoint = urljoin(self.endpoint, 'settings/')
647
- self.api.put(endpoint, self.settings)
648
-
649
650
 
650
651
  def get_markers(self) -> Dict:
651
652
  """
@@ -608,10 +608,7 @@ class Raster(Base):
608
608
  'min_zoom', 'max_zoom', 'use_cache', 'cache_until_zoom'
609
609
  }
610
610
 
611
- settings = {
612
- 'visual_settings': {},
613
- 'tile_settings': {}
614
- }
611
+ settings = self.settings
615
612
 
616
613
  for key, value in kwargs.items():
617
614
  if key in visual_settings:
@@ -620,7 +617,7 @@ class Raster(Base):
620
617
  settings['tile_settings'][key] = value
621
618
 
622
619
 
623
- super()._set_settings(self.endpoint, settings)
620
+ return super()._set_settings(self.endpoint, settings)
624
621
 
625
622
 
626
623
  def share(self, users: List['User']) -> None:
@@ -10,6 +10,7 @@ if TYPE_CHECKING:
10
10
  from .vectorlayer import VectorLayer
11
11
  from .raster import Raster
12
12
  from .model3d import Model
13
+ from .file import File
13
14
 
14
15
  class Task(Base):
15
16
  """
@@ -45,9 +46,9 @@ class Task(Base):
45
46
 
46
47
 
47
48
  @property
48
- def publised_asset(self) -> Union['VectorLayer', 'Raster', 'Model', None]:
49
+ def output_asset(self) -> Union['VectorLayer', 'Raster', 'Model', 'File', None]:
49
50
  """
50
- Published asset property
51
+ output asset property
51
52
 
52
53
  Returns:
53
54
  VectorLayer | Raster | Model | None: if task type is publish, it returns the published layer
@@ -57,14 +58,20 @@ class Task(Base):
57
58
  >>> from geobox.task import Task
58
59
  >>> client = GeoboxClient()
59
60
  >>> task = Task.get_task(client, uuid="12345678-1234-5678-1234-567812345678")
60
- >>> task.publised_asset
61
+ >>> task.output_asset
61
62
  """
62
- if self.name == 'import:publish-file-to-raster-dataset':
63
- return self.api.get_raster(uuid=self.data['result']['raster_uuid'])
64
- elif self.name == 'import:publish-file-to-vector-layer':
63
+ if self.data.get('result', {}).get('layer_uuid'):
65
64
  return self.api.get_vector(uuid=self.data['result']['layer_uuid'])
66
- elif self.name == 'import:publish-file-to-3d-model':
65
+
66
+ elif self.data.get('result', {}).get('raster_uuid'):
67
+ return self.api.get_raster(uuid=self.data['result']['raster_uuid'])
68
+
69
+ elif self.data.get('result', {}).get('model_uuid'):
67
70
  return self.api.get_model(uuid=self.data['result']['model_uuid'])
71
+
72
+ elif self.data.get('result', {}).get('file_uuid'):
73
+ return self.api.get_file(uuid=self.data['result']['file_uuid'])
74
+
68
75
  else:
69
76
  return None
70
77
 
@@ -36,6 +36,17 @@ class User(Base):
36
36
  str: A string representation of the User instance.
37
37
  """
38
38
  return f'User(id={self.id}, first_name={self.first_name}, last_name={self.last_name})'
39
+
40
+
41
+ @property
42
+ def role(self) -> 'UserRole':
43
+ """
44
+ User role property
45
+
46
+ Returns:
47
+ UserRole: the user role
48
+ """
49
+ return UserRole(self.data.get('role')) if self.data.get('role') else None
39
50
 
40
51
 
41
52
  @property
@@ -94,6 +105,7 @@ class User(Base):
94
105
  """
95
106
  params = {
96
107
  'f': 'json',
108
+ 'status': kwargs.get('status').value if kwargs.get('status') else None,
97
109
  'q': kwargs.get('q'),
98
110
  'search': kwargs.get('search'),
99
111
  'search_fields': kwargs.get('search_fields'),
@@ -264,18 +276,33 @@ class User(Base):
264
276
  >>> user.update_user(status=UserStatus.PENDING)
265
277
  """
266
278
  data = {
267
- "csrf_token": kwargs.get('csrf_token'),
268
279
  "username": kwargs.get('username'),
269
280
  "email": kwargs.get('email'),
270
281
  "first_name": kwargs.get('first_name'),
271
282
  "last_name": kwargs.get('last_name'),
272
- "mobile": kwargs.get('mobile'),
273
283
  "status": kwargs.get('status').value if kwargs.get('status') else None,
274
284
  "role": kwargs.get('role').value if kwargs.get('role') else None,
275
- "plan_id": kwargs.get('plan').id if kwargs.get('plan') else None,
276
- "expiration_date": kwargs.get('expiration_date')
277
285
  }
278
- return super()._update(self.endpoint, data)
286
+ data = clean_data(data)
287
+
288
+ try:
289
+ data['mobile'] = None if kwargs['mobile'] == '' else kwargs['mobile']
290
+ except:
291
+ pass
292
+
293
+ try:
294
+ data['plan_id'] = None if kwargs['plan'] == '' else kwargs['plan'].id
295
+ except:
296
+ pass
297
+
298
+ try:
299
+ data['expiration_date'] = None if kwargs['expiration_date'] == '' else kwargs['expiration_date']
300
+ except:
301
+ pass
302
+
303
+ response = self.api.put(self.endpoint, data)
304
+ self._update_properties(response)
305
+ return response
279
306
 
280
307
 
281
308
  def delete(self) -> None:
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
16
16
  from .api import GeoboxClient
17
17
  from .view import VectorLayerView
18
18
  from .user import User
19
+ from .file import File
19
20
 
20
21
  class VectorLayer(Base):
21
22
  """
@@ -226,6 +227,7 @@ class VectorLayer(Base):
226
227
  display_name: str = None,
227
228
  description: str = None,
228
229
  has_z: bool = False,
230
+ temporary: bool = False,
229
231
  fields: List = None) -> 'VectorLayer':
230
232
  """
231
233
  Create a new vector layer.
@@ -237,6 +239,7 @@ class VectorLayer(Base):
237
239
  display_name (str, optional): A human-readable name for the layer. default is None.
238
240
  description (str, optional): A description of the layer. default is None.
239
241
  has_z (bool, optional): Whether the layer includes Z coordinates. default is False.
242
+ temporary (bool, optional): Whether to create a temporary layer. temporary layers will be deleted after 24 hours. default is False.
240
243
  fields (List, optional): List of field definitions for the layer. default is None.
241
244
 
242
245
  Returns:
@@ -255,6 +258,7 @@ class VectorLayer(Base):
255
258
  ... display_name="My Layer",
256
259
  ... description="This is a description of my layer",
257
260
  ... has_z=False,
261
+ ... temporary=True,
258
262
  ... fields=[{"name": "my_field", "datatype": "FieldTypeString"}])
259
263
  or
260
264
  >>> layer = client.create_vector(name="my_layer",
@@ -262,6 +266,7 @@ class VectorLayer(Base):
262
266
  ... display_name="My Layer",
263
267
  ... description="This is a description of my layer",
264
268
  ... has_z=False,
269
+ ... temporary=True,
265
270
  ... fields=[{"name": "my_field", "datatype": "FieldTypeString"}])
266
271
  """
267
272
  data = {
@@ -270,6 +275,7 @@ class VectorLayer(Base):
270
275
  "display_name": display_name,
271
276
  "description": description,
272
277
  "has_z": has_z,
278
+ "temporary": temporary,
273
279
  "fields": fields
274
280
  }
275
281
  return super()._create(api=api,
@@ -332,7 +338,8 @@ class VectorLayer(Base):
332
338
  Make the layer permanent.
333
339
  """
334
340
  endpoint = urljoin(self.endpoint, 'makePermanent/')
335
- self.api.post(endpoint, is_json=False)
341
+ response = self.api.post(endpoint, is_json=False)
342
+ self._update_properties(response)
336
343
 
337
344
 
338
345
  def share(self, users: List['User']) -> None:
@@ -447,7 +454,10 @@ class VectorLayer(Base):
447
454
  >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
448
455
  >>> layer.wfs
449
456
  """
450
- return f'{self.api.base_url}{self.endpoint}wfs/'
457
+ if self.api.access_token:
458
+ return f'{self.api.base_url}{self.endpoint}wfs/'
459
+ elif self.api.apikey:
460
+ return f'{self.api.base_url}{self.endpoint}apikey:{self.api.apikey}/wfs/'
451
461
 
452
462
 
453
463
  def get_fields(self) -> List['Field']:
@@ -470,41 +480,56 @@ class VectorLayer(Base):
470
480
  factory_func=lambda api, item: Field(layer=self, data_type=FieldType(item['datatype']), field_id=item['id'], data=item))
471
481
 
472
482
 
473
- def get_field(self, field_id: str = None, name: str = None) -> 'Field':
483
+ def get_field(self, field_id: int) -> 'Field':
474
484
  """
475
485
  Get a specific field by its ID.
476
486
 
477
487
  Args:
478
- field_id (str, optional): The ID of the field to retrieve.
479
- name (str, optional): The name of the field to retrieve.
488
+ field_id (int, optional): The ID of the field to retrieve.
480
489
 
481
490
  Returns:
482
491
  Field: The requested field instance.
483
492
 
484
493
  Raises:
485
- ValueError: Either id or name must be provided.
486
- NotFoundError: If the field with the specified ID or name is not found.
494
+ NotFoundError: If the field with the specified ID is not found.
487
495
 
488
496
  Example:
489
497
  >>> from geobox import GeoboxClient
490
498
  >>> from geobox.vectorlayer import VectorLayer
491
499
  >>> client = GeoboxClient()
492
500
  >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
493
- >>> field = layer.get_field(field_id="1")
501
+ >>> field = layer.get_field(field_id=1)
502
+ """
503
+ field = next((f for f in self.get_fields() if f.id == field_id), None)
504
+ if not field:
505
+ raise NotFoundError(f'Field with ID {field_id} not found in layer {self.name}')
506
+
507
+ return field
508
+
509
+
510
+ def get_field_by_name(self, name: str) -> 'Field':
494
511
  """
495
- if not (field_id or name):
496
- raise ValueError('Either "field_id" or "name" must be provided')
512
+ Get a specific field by its name.
497
513
 
498
- if field_id:
499
- field = next((f for f in self.get_fields() if str(f.id) == str(field_id)), None)
500
- elif name:
501
- field = next((f for f in self.get_fields() if f.name == name), None)
514
+ Args:
515
+ name (str, optional): The name of the field to retrieve.
502
516
 
517
+ Returns:
518
+ Field: The requested field instance.
519
+
520
+ Raises:
521
+ NotFoundError: If the field with the specified name is not found.
522
+
523
+ Example:
524
+ >>> from geobox import GeoboxClient
525
+ >>> from geobox.vectorlayer import VectorLayer
526
+ >>> client = GeoboxClient()
527
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
528
+ >>> field = layer.get_field_by_name(name='test')
529
+ """
530
+ field = next((f for f in self.get_fields() if f.name == name), None)
503
531
  if not field:
504
- if field_id:
505
- raise NotFoundError(f'Field with ID {field_id} not found in layer {self.name}')
506
- elif name:
507
- raise NotFoundError(f'Field with name "{name}" not found in layer {self.name}')
532
+ raise NotFoundError(f"Field with name '{name}' not found in layer {self.name}")
508
533
 
509
534
  return field
510
535
 
@@ -542,7 +567,7 @@ class VectorLayer(Base):
542
567
  bbox_srid: int = None,
543
568
  feature_ids: List = None,
544
569
  run_async: bool = True,
545
- user_id: int = None) -> 'Task':
570
+ user_id: int = None) -> Union['Task', Dict]:
546
571
  """
547
572
  Calculate values for a field based on an expression.
548
573
 
@@ -557,7 +582,7 @@ class VectorLayer(Base):
557
582
  user_id (int, optional): Specific user. privileges required.
558
583
 
559
584
  Returns:
560
- Task: The task instance of the calculation operation.
585
+ Task | Dict: The task instance of the calculation operation or the api response if the run_async=False.
561
586
 
562
587
  Example:
563
588
  >>> from geobox import GeoboxClient
@@ -585,8 +610,11 @@ class VectorLayer(Base):
585
610
 
586
611
  endpoint = urljoin(self.endpoint, 'calculateField/')
587
612
  response = self.api.post(endpoint, data, is_json=False)
588
- task = Task.get_task(self.api, response.get('task_uuid'))
589
- return task
613
+ if run_async:
614
+ task = Task.get_task(self.api, response.get('task_id'))
615
+ return task
616
+
617
+ return response
590
618
 
591
619
 
592
620
  def get_features(self, **kwargs) -> Union[List['Feature'], int]:
@@ -713,21 +741,21 @@ class VectorLayer(Base):
713
741
  return Feature.create_feature(self, geojson)
714
742
 
715
743
 
716
- def delete_features(self, q: str = None, bbox: List[float] = None, bbox_srid: int = None, feature_ids: List[int] = None,
717
- run_async: bool = True, user_id: int = None) -> 'Task':
744
+ def delete_features(self, q: str = None, bbox: str = None, bbox_srid: int = None, feature_ids: List[int] = None,
745
+ run_async: bool = True, user_id: int = None) -> Union['Task', Dict]:
718
746
  """
719
747
  Delete features from the layer based on specified criteria.
720
748
 
721
749
  Args:
722
- q (Optional[str]): Query to filter features to delete.
723
- bbox (Optional[List[float]]): Bounding box to filter features.
724
- bbox_srid (Optional[int]): Spatial reference ID for the bounding box.
725
- feature_ids (Optional[List[int]]): List of specific feature IDs to delete.
726
- run_async (Optional[bool]): Whether to run the deletion asynchronously. default is True.
727
- user_id (Optional[int]): Specific user. privileges required.
750
+ q (str, optional): Query to filter features to delete.
751
+ bbox (str, optional): Comma seprated bbox.
752
+ bbox_srid (int, optional): Spatial reference ID for the bounding box.
753
+ feature_ids (str, optional): Comma seprated feature ids.
754
+ run_async (bool, optional): Whether to run the deletion asynchronously. default is True.
755
+ user_id (int, optional): Specific user. privileges required.
728
756
 
729
757
  Returns:
730
- Task: The task instance of the deletion operation.
758
+ Task | Dict: The task instance of the deletion operation or the api response if run_async=False.
731
759
 
732
760
  Raises:
733
761
  ValidationError: If the deletion parameters are invalid.
@@ -738,9 +766,9 @@ class VectorLayer(Base):
738
766
  >>> client = GeoboxClient()
739
767
  >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
740
768
  >>> layer.delete_features(q="name like 'my_layer'",
741
- ... bbox=[10, 20, 30, 40],
769
+ ... bbox='10, 20, 30, 40',
742
770
  ... bbox_srid=3857,
743
- ... feature_ids=[1, 2, 3],
771
+ ... feature_ids='1, 2, 3',
744
772
  ... run_async=True)
745
773
  """
746
774
  data = clean_data({
@@ -754,19 +782,29 @@ class VectorLayer(Base):
754
782
 
755
783
  endpoint = urljoin(self.endpoint, 'deleteFeatures/')
756
784
  response = self.api.post(endpoint, data, is_json=False)
757
- task = Task.get_task(self.api, response.get('task_id'))
758
- return task
785
+ if run_async:
786
+ task = Task.get_task(self.api, response.get('task_id'))
787
+ return task
759
788
 
789
+ return response
760
790
 
761
- def import_features(self, file_uuid: str, input_geom_type: 'InputGeomType', input_layer_name: str = None, input_dataset: str = None,
762
- user_id: int = None, input_srid: int = None, file_encoding: str = "utf-8",
763
- replace_domain_codes_by_values: bool = False, report_errors: bool = True) -> 'Task':
791
+
792
+ def import_features(self,
793
+ file: 'File',
794
+ input_geom_type: 'InputGeomType' = None,
795
+ input_layer_name: str = None,
796
+ input_dataset: str = None,
797
+ user_id: int = None,
798
+ input_srid: int = None,
799
+ file_encoding: str = "utf-8",
800
+ replace_domain_codes_by_values: bool = False,
801
+ report_errors: bool = True) -> 'Task':
764
802
  """
765
803
  Import features from a file into the layer.
766
804
 
767
805
  Args:
768
- file_uuid (str): UUID of the uploaded file to import.
769
- input_geom_type (InputGeomType): Type of geometry in the input file.
806
+ file (File): file object to import.
807
+ input_geom_type (InputGeomType, optional): Type of geometry in the input file.
770
808
  input_layer_name (str, optional): Name of the layer in the input file.
771
809
  input_dataset (str, optional): Name of the dataset in the input file.
772
810
  user_id (int, optional): Specific user. privileges required.
@@ -786,7 +824,8 @@ class VectorLayer(Base):
786
824
  >>> from geobox.vectorlayer import VectorLayer
787
825
  >>> client = GeoboxClient()
788
826
  >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
789
- >>> task = layer.import_features(file_uuid="12345678-1234-5678-1234-567812345678",
827
+ >>> file = client.get_file(uuid="12345678-1234-5678-1234-567812345678")
828
+ >>> task = layer.import_features(file=file,
790
829
  ... input_geom_type=InputGeomType.POINT,
791
830
  ... input_layer_name="my_layer",
792
831
  ... input_dataset="my_dataset",
@@ -796,7 +835,7 @@ class VectorLayer(Base):
796
835
  ... report_errors=True)
797
836
  """
798
837
  data = clean_data({
799
- "file_uuid": file_uuid,
838
+ "file_uuid": file.uuid,
800
839
  "input_layer": input_layer_name,
801
840
  "input_geom_type": input_geom_type.value if isinstance(input_geom_type, InputGeomType) else input_geom_type,
802
841
  "replace_domain_codes_by_values": replace_domain_codes_by_values,
@@ -813,9 +852,17 @@ class VectorLayer(Base):
813
852
  return task
814
853
 
815
854
 
816
- def export_features(self, out_filename: str, out_format: 'FileOutputFormat', replace_domain_codes_by_values: bool = False,
817
- run_async: bool = True, bbox: List[float] = None, out_srid: int = None, zipped: bool = True,
818
- feature_ids: List[int] = None, bbox_srid: int = None, q: str = None, fields: List[str] = None) -> 'Task':
855
+ def export_features(self,
856
+ out_filename: str,
857
+ out_format: 'FileOutputFormat',
858
+ replace_domain_codes_by_values: bool = False,
859
+ run_async: bool = True,
860
+ bbox: List[float] = None,
861
+ out_srid: int = None,
862
+ zipped: bool = True,
863
+ feature_ids: List[int] = None,
864
+ bbox_srid: int = None,
865
+ q: str = None, fields: List[str] = None) -> Union['Task', Dict]:
819
866
  """
820
867
  Export features from the layer to a file.
821
868
 
@@ -833,7 +880,7 @@ class VectorLayer(Base):
833
880
  fields (List[str], optional): List of fields to include in the export.
834
881
 
835
882
  Returns:
836
- Task: The task instance of the export operation.
883
+ Task | Dict: The task instance of the export operation or the api response if run_async=False.
837
884
 
838
885
  Raises:
839
886
  ValidationError: If the export parameters are invalid.
@@ -868,8 +915,11 @@ class VectorLayer(Base):
868
915
 
869
916
  endpoint = urljoin(self.endpoint, 'export/')
870
917
  response = self.api.post(endpoint, data, is_json=False)
871
- task = Task.get_task(self.api, response.get('task_id'))
872
- return task
918
+ if run_async:
919
+ task = Task.get_task(self.api, response.get('task_id'))
920
+ return task
921
+
922
+ return response
873
923
 
874
924
 
875
925
  def create_view(self, name: str, display_name: str = None, description: str = None,
@@ -1031,11 +1081,7 @@ class VectorLayer(Base):
1031
1081
  edit_settings = {'editable', 'edit_geometry', 'editable_attributes', 'allow_insert', 'allow_delete'}
1032
1082
  tile_settings = {'min_zoom', 'max_zoom', 'max_features', 'filter_features', 'fields', 'use_cache', 'cache_until_zoom'}
1033
1083
 
1034
- settings = {
1035
- 'general_settings': {},
1036
- 'edit_settings': {},
1037
- 'tile_settings': {}
1038
- }
1084
+ settings = self.settings
1039
1085
 
1040
1086
  for key, value in kwargs.items():
1041
1087
  if key in general_settings: