pygeobox 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pygeobox/file.py ADDED
@@ -0,0 +1,494 @@
1
+ from urllib.parse import urljoin
2
+ from typing import Optional, Dict, List, Union, TYPE_CHECKING
3
+ import os
4
+ import mimetypes
5
+ import requests
6
+ import sys
7
+
8
+ from .base import Base
9
+ from .enums import FileType, PublishFileType, InputGeomType
10
+ from .utils import clean_data
11
+ from .task import Task
12
+
13
+ if TYPE_CHECKING:
14
+ from . import GeoboxClient
15
+ from .user import User
16
+
17
+ class File(Base):
18
+ """
19
+ A class to interact with files in Geobox.
20
+
21
+ This class provides functionality to interact with files in Geobox.
22
+ It supports various operations including CRUD operations on files, as well as advanced operations like publishing, and sharing files.
23
+ It also provides properties to access the file data, and a method to download the file.
24
+ """
25
+ BASE_ENDPOINT: str = 'files/'
26
+
27
+ def __init__(self,
28
+ api: 'GeoboxClient',
29
+ uuid: str,
30
+ data: Optional[Dict] = {}):
31
+ """
32
+ Constructs all the necessary attributes for the File object.
33
+
34
+ Args:
35
+ api (Api): The API instance.
36
+ uuid (str): The UUID of the file.
37
+ data (Dict, optional): The data of the file.
38
+ """
39
+ super().__init__(api, uuid=uuid, data=data)
40
+
41
+
42
+ def __repr__(self) -> str:
43
+ """
44
+ Return a string representation of the File object.
45
+
46
+ Returns:
47
+ str: A string representation of the File object.
48
+ """
49
+ return f"File(file_name={self.name}, file_type={self.file_type})"
50
+
51
+
52
+ @property
53
+ def layers(self) -> List[Dict]:
54
+ """
55
+ Get the layers of the file.
56
+
57
+ Returns:
58
+ List[Dict]: The layers of the file.
59
+
60
+ Example:
61
+ >>> from geobox import GeoboxClient
62
+ >>> client = GeoboxClient()
63
+ >>> file = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
64
+ >>> file.layers
65
+ """
66
+ return self.data.get('layers', {}).get('layers', [])
67
+
68
+
69
+ @classmethod
70
+ def upload_file(cls, api: 'GeoboxClient', path: str, user_id: int = None, scan_archive: bool = True) -> 'File':
71
+ """
72
+ Upload a file to the GeoBox API.
73
+
74
+ Args:
75
+ api (GeoboxClient): The API instance.
76
+ path (str): The path to the file to upload.
77
+ user_id (int, optional): specific user. privileges required.
78
+ scan_archive (bool, optional): Whether to scan the archive for layers. default: True
79
+
80
+ Returns:
81
+ File: The uploaded file instance.
82
+
83
+ Raises:
84
+ ValueError: If the file type is invalid.
85
+ FileNotFoundError: If the file does not exist.
86
+
87
+ Example:
88
+ >>> from geobox import GeoboxClient
89
+ >>> from geobox.file import File
90
+ >>> client = GeoboxClient()
91
+ >>> file = File.upload_file(client, path='path/to/file.shp')
92
+ or
93
+ >>> file = client.upload_file(path='path/to/file.shp')
94
+ """
95
+ # Check if the file exists
96
+ if not os.path.exists(path):
97
+ raise FileNotFoundError(f"File not found: {path}")
98
+
99
+ # Check if the file type is valid
100
+ FileType(os.path.splitext(path)[1])
101
+
102
+ data = clean_data({
103
+ "user_id": user_id,
104
+ "scan_archive": scan_archive
105
+ })
106
+
107
+ endpoint = cls.BASE_ENDPOINT
108
+ with open(path, 'rb') as f:
109
+ files = {'file': f}
110
+ file_data = api.post(endpoint, data, is_json=False, files=files)
111
+ return cls(api, file_data['uuid'], file_data)
112
+
113
+
114
+ @classmethod
115
+ def get_files(cls, api:'GeoboxClient', **kwargs) -> Union[List['File'], int]:
116
+ """
117
+ Retrieves a list of files.
118
+
119
+ Args:
120
+ api (GeoboxClient): The GeoboxClient instance for making requests.
121
+
122
+ Keyword Args:
123
+ q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
124
+ 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.
125
+ search_fields (str): comma separated list of fields for searching
126
+ 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.
127
+ return_count (bool): if true, the total number of results will be returned. default is False.
128
+ skip (int): number of results to skip. default is 0.
129
+ limit (int): number of results to return. default is 10.
130
+ user_id (int): filter by user id.
131
+ shared (bool): Whether to return shared files. default is False.
132
+
133
+ Returns:
134
+ List[File] | int: A list of File objects or the total number of results.
135
+
136
+ Example:
137
+ >>> from geobox import GeoboxClient
138
+ >>> from geobox.file import File
139
+ >>> client = GeoboxClient()
140
+ >>> files = File.get_files(client, search_fields='name', search='GIS', order_by='name', skip=10, limit=10)
141
+ or
142
+ >>> files = client.get_files(search_fields='name', search='GIS', order_by='name', skip=10, limit=10)
143
+ """
144
+ params = {
145
+ 'f': 'json',
146
+ 'q': kwargs.get('q', None),
147
+ 'search': kwargs.get('search', None),
148
+ 'search_fields': kwargs.get('search_fields', None),
149
+ 'order_by': kwargs.get('order_by', None),
150
+ 'return_count': kwargs.get('return_count', False),
151
+ 'skip': kwargs.get('skip', 0),
152
+ 'limit': kwargs.get('limit', 10),
153
+ 'user_id': kwargs.get('user_id', None),
154
+ 'shared': kwargs.get('shared', False)
155
+ }
156
+ return super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: cls(api, item['uuid'], item))
157
+
158
+
159
+ @classmethod
160
+ def get_file(cls, api: 'GeoboxClient', uuid: str, user_id: int = None) -> 'File':
161
+ """
162
+ Retrieves a file by its UUID.
163
+
164
+ Args:
165
+ api (Api): The API instance.
166
+ uuid (str): The UUID of the file.
167
+ user_id (int, optional): specific user. privileges required.
168
+
169
+ Returns:
170
+ File: The retrieved file instance.
171
+
172
+ Raises:
173
+ NotFoundError: If the file with the specified UUID is not found.
174
+
175
+ Example:
176
+ >>> from geobox import GeoboxClient
177
+ >>> from geobox.file import File
178
+ >>> client = GeoboxClient()
179
+ >>> file = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
180
+ or
181
+ >>> file = client.get_file(uuid="12345678-1234-5678-1234-567812345678")
182
+ """
183
+ params = {
184
+ 'f': 'json',
185
+ 'user_id': user_id
186
+ }
187
+ return super()._get_detail(api, cls.BASE_ENDPOINT, f'{uuid}/info', params, factory_func=lambda api, item: File(api, item['uuid'], item))
188
+
189
+
190
+ @classmethod
191
+ def get_file_by_name(cls, api: 'GeoboxClient', name: str, user_id: int = None) -> Union['File', None]:
192
+ """
193
+ Get a file by name
194
+
195
+ Args:
196
+ api (GeoboxClient): The GeoboxClient instance for making requests.
197
+ name (str): the name of the file to get
198
+ user_id (int, optional): specific user. privileges required.
199
+
200
+ Returns:
201
+ File | None: returns the file if a file matches the given name, else None
202
+
203
+ Example:
204
+ >>> from geobox import GeoboxClient
205
+ >>> from geobox.file import File
206
+ >>> client = GeoboxClient()
207
+ >>> file = File.get_file_by_name(client, name='test')
208
+ or
209
+ >>> file = client.get_file_by_name(name='test')
210
+ """
211
+ files = cls.get_files(api, q=f"name = '{name}'", user_id=user_id)
212
+ if files and files[0].name == name:
213
+ return files[0]
214
+ else:
215
+ return None
216
+
217
+
218
+ def _get_save_path(self, save_path: str = None) -> str:
219
+ """
220
+ Get the path where the file should be saved.
221
+
222
+ Args:
223
+ save_path (str, optional): The path to save the file.
224
+
225
+ Returns:
226
+ str: The path where the file is saved.
227
+
228
+ Raises:
229
+ ValueError: If save_path does not end with a '/'.
230
+
231
+ Example:
232
+ >>> from geobox import GeoboxClient
233
+ >>> from geobox.file import File
234
+ >>> from geobox import GeoboxClient
235
+ >>> client = GeoboxClient()
236
+ >>> file_path = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
237
+ """
238
+ # Get the original filename from data or use uuid
239
+ if self.name:
240
+ filename = f"{self.name.split('.')[0]}" if len(self.name.split('.')) > 1 else f'{self.name}'
241
+ else:
242
+ filename = f'{self.uuid}'
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}{filename}'
247
+
248
+ if save_path and not save_path.endswith('/'):
249
+ raise ValueError("save_path must end with a '/'")
250
+
251
+ return os.path.join(os.getcwd(), filename)
252
+
253
+
254
+ def _get_file_ext(self, response: requests.Response) -> str:
255
+ """
256
+ Get the file extension of the response.
257
+
258
+ Args:
259
+ response (requests.Response): The response of the request.
260
+
261
+ Returns:
262
+ str: The file extension of the response.
263
+ """
264
+ ext = ""
265
+ if 'Content-Disposition' in response.headers:
266
+ content_disposition = response.headers['Content-Disposition']
267
+ if 'filename=' in content_disposition:
268
+ filename = content_disposition.split('filename=')[-1].strip().strip('"')
269
+ ext = f".{filename.split('.')[-1]}"
270
+
271
+ else:
272
+ content_type = response.headers.get("Content-Type", "")
273
+ ext = mimetypes.guess_extension(content_type.split(";")[0])
274
+
275
+ return ext
276
+
277
+
278
+ def _create_progress_bar(self) -> 'tqdm':
279
+ """Creates a progress bar for the task."""
280
+ try:
281
+ from tqdm.auto import tqdm
282
+ except ImportError:
283
+ from .api import logger
284
+ logger.warning("[tqdm] extra is required to show the progress bar. install with: pip insatll pygeobox[tqdm]")
285
+ return None
286
+
287
+ return tqdm(unit="B",
288
+ total=int(self.size),
289
+ file=sys.stdout,
290
+ dynamic_ncols=True,
291
+ desc="Downloading",
292
+ unit_scale=True,
293
+ unit_divisor=1024,
294
+ ascii=True
295
+ )
296
+
297
+
298
+ def download(self, save_path: str = None, progress_bar: bool = True) -> str:
299
+ """
300
+ Download a file and save it to the specified path.
301
+
302
+ Args:
303
+ save_path (str, optional): Path where the file should be saved.
304
+ If not provided, it saves to the current working directory
305
+ using the original filename and appropriate extension.
306
+ progress_bar (bool, optional): Whether to show a progress bar. default: True
307
+
308
+ Returns:
309
+ str: Path where the file was saved
310
+
311
+ Raises:
312
+ ValueError: If uuid is not set
313
+ OSError: If there are issues with file operations
314
+
315
+ Example:
316
+ >>> from geobox import GeoboxClient
317
+ >>> from geobox.file import File
318
+ >>> client = GeoboxClient()
319
+ >>> file = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
320
+ >>> file.download(save_path='path/to/save/')
321
+ """
322
+ if not self.uuid:
323
+ raise ValueError("File UUID is required to download the file")
324
+
325
+ save_path = self._get_save_path(save_path)
326
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
327
+
328
+ with self.api.get(f"{self.endpoint}download/", stream=True) as response, \
329
+ open(save_path, 'wb') as f:
330
+ pbar = self._create_progress_bar() if progress_bar else None
331
+ for chunk in response.iter_content(chunk_size=8192):
332
+ f.write(chunk)
333
+ if pbar:
334
+ pbar.update(len(chunk))
335
+ pbar.refresh()
336
+ if pbar:
337
+ pbar.close()
338
+
339
+ final_path = os.path.abspath(save_path) + self._get_file_ext(response)
340
+ os.rename(os.path.abspath(save_path), os.path.abspath(final_path))
341
+ return os.path.abspath(final_path)
342
+
343
+
344
+ def delete(self) -> None:
345
+ """
346
+ Deletes the file.
347
+
348
+ Returns:
349
+ None
350
+
351
+ Example:
352
+ >>> from geobox import GeoboxClient
353
+ >>> from geobox.file import File
354
+ >>> client = GeoboxClient()
355
+ >>> file = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
356
+ >>> file.delete()
357
+ """
358
+ return super().delete(self.endpoint)
359
+
360
+
361
+ def publish(self,
362
+ publish_as: 'PublishFileType',
363
+ name: str,
364
+ input_geom_type: 'InputGeomType' = None,
365
+ input_layer: str = None,
366
+ input_dataset: str = None,
367
+ user_id: int = None,
368
+ input_srid: int = None,
369
+ file_encoding: str = "utf-8",
370
+ replace_domain_codes_by_values: bool = False,
371
+ report_errors: bool = True,
372
+ as_terrain: bool = False) -> 'Task':
373
+ """
374
+ Publishes a file as a layer.
375
+
376
+ Args:
377
+ publish_as (PublishFileType): The type of layer to publish as.
378
+ name (str): The name of the layer.
379
+ input_geom_type (InputGeomType, optional): The geometry type of the layer.
380
+ input_layer (str, optional): The name of the input layer.
381
+ input_dataset (str, optional): The name of the input dataset.
382
+ user_id (int, optional): Specific user. privileges required.
383
+ input_srid (int, optional): The SRID of the layer.
384
+ file_encoding (str, optional): The encoding of the file. default is "utf-8".
385
+ replace_domain_codes_by_values (bool, optional): Whether to replace domain codes by values. default is False.
386
+ report_errors (bool, optional): Whether to report errors. default is True.
387
+ as_terrain (bool, optional): Whether to publish as terrain. default is False.
388
+
389
+ Returns:
390
+ Task: The task object.
391
+
392
+ Raises:
393
+ ValueError: If the publish_as is not a valid PublishFileType.
394
+
395
+ Example:
396
+ >>> from geobox import GeoboxClient
397
+ >>> from geobox.file import File
398
+ >>> client = GeoboxClient()
399
+ >>> file = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
400
+ >>> file.publish(publish_as=PublishFileType.VECTOR,
401
+ ... layer_name='my_layer',
402
+ ... input_geom_type=InputGeomType.POINT,
403
+ ... input_layer='layer1',
404
+ ... input_dataset='dataset1',
405
+ ... input_srid=4326,
406
+ ... file_encoding='utf-8')
407
+ """
408
+ data = clean_data({
409
+ "publish_as": publish_as.value if isinstance(publish_as, PublishFileType) else publish_as,
410
+ "layer_name": name,
411
+ "input_layer": input_layer,
412
+ "input_geom_type": input_geom_type.value if isinstance(input_geom_type, InputGeomType) else input_geom_type,
413
+ "replace_domain_codes_by_values": replace_domain_codes_by_values,
414
+ "input_dataset": self.name if not input_dataset else input_dataset,
415
+ "user_id": user_id,
416
+ "input_srid": input_srid,
417
+ "file_encoding": file_encoding,
418
+ "report_errors": report_errors,
419
+ "as_terrain": as_terrain
420
+ })
421
+
422
+ endpoint = urljoin(self.endpoint, 'publish/')
423
+ response = self.api.post(endpoint, data, is_json=False)
424
+ task = Task.get_task(self.api, response.get('task_id'))
425
+ return task
426
+
427
+
428
+ def share(self, users: List['User']) -> None:
429
+ """
430
+ Shares the file with specified users.
431
+
432
+ Args:
433
+ users (List[User]): The list of users objects to share the file with.
434
+
435
+ Returns:
436
+ None
437
+
438
+ Example:
439
+ >>> from geobox import GeoboxClient
440
+ >>> from geobox.file import File
441
+ >>> client = GeoboxClient()
442
+ >>> file = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
443
+ >>> users = client.search_users(search='John')
444
+ >>> file.share(users=users)
445
+ """
446
+ super()._share(self.endpoint, users)
447
+
448
+
449
+ def unshare(self, users: List['User']) -> None:
450
+ """
451
+ Unshares the file with specified users.
452
+
453
+ Args:
454
+ users (List[User]): The list of users objects to unshare the file with.
455
+
456
+ Returns:
457
+ None
458
+
459
+ Example:
460
+ >>> from geobox import GeoboxClient
461
+ >>> from geobox.file import File
462
+ >>> client = GeoboxClient()
463
+ >>> file = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
464
+ >>> users = client.search_users(search='John')
465
+ >>> file.unshare(users=users)
466
+ """
467
+ super()._unshare(self.endpoint, users)
468
+
469
+
470
+ def get_shared_users(self, search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
471
+ """
472
+ Retrieves the list of users the file is shared with.
473
+
474
+ Args:
475
+ search (str, optional): The search query.
476
+ skip (int, optional): The number of users to skip.
477
+ limit (int, optional): The maximum number of users to retrieve.
478
+
479
+ Returns:
480
+ List[User]: The list of shared users.
481
+
482
+ Example:
483
+ >>> from geobox import GeoboxClient
484
+ >>> from geobox.file import File
485
+ >>> client = GeoboxClient()
486
+ >>> file = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
487
+ >>> file.get_shared_users(search='John', skip=0, limit=10)
488
+ """
489
+ params = {
490
+ 'search': search,
491
+ 'skip': skip,
492
+ 'limit': limit
493
+ }
494
+ return super()._get_shared_users(self.endpoint, params)
pygeobox/log.py ADDED
@@ -0,0 +1,101 @@
1
+ from urllib.parse import urljoin
2
+ from typing import Optional, Dict, List, Union, TYPE_CHECKING
3
+ from datetime import datetime
4
+
5
+ from .base import Base
6
+
7
+ if TYPE_CHECKING:
8
+ from . import GeoboxClient
9
+
10
+
11
+ class Log(Base):
12
+
13
+ BASE_ENDPOINT = 'logs/'
14
+
15
+ def __init__(self,
16
+ api: 'GeoboxClient',
17
+ log_id: int,
18
+ data: Optional[Dict] = {}):
19
+ """
20
+ Constructs all the necessary attributes for the Log object.
21
+
22
+ Args:
23
+ api (Api): The GeoboxClient instance for making requests.
24
+ log_id (int): The id of the log.
25
+ data (Dict, optional): The data of the log.
26
+ """
27
+ super().__init__(api, data=data)
28
+ self.log_id = log_id
29
+ self.endpoint = f"{self.BASE_ENDPOINT}{self.log_id}"
30
+
31
+
32
+ def __repr__(self) -> str:
33
+ """
34
+ Return a string representation of the Log object.
35
+
36
+ Returns:
37
+ str: A string representation of the Log object.
38
+ """
39
+ return f"Log(id={self.log_id}, activity_type={self.activity_type})"
40
+
41
+
42
+ @classmethod
43
+ def get_logs(cls, api: 'GeoboxClient', **kwargs) -> List['Log']:
44
+ """
45
+ Get a list of Logs
46
+
47
+ Args:
48
+ api (GeoboxClient): The GeoboxClient instance for making requests.
49
+
50
+ Keyword Args:
51
+ search (str): search term for keyword-based searching among all textual fields
52
+ 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.
53
+ skip (int): Number of items to skip. default is 0.
54
+ limit (int): Number of items to return. default is 10.
55
+ user_id (int): Specific user. Privileges required.
56
+ from_date (datetime): datetime object in this format: "%Y-%m-%dT%H:%M:%S.%f".
57
+ to_date (datetime): datetime object in this format: "%Y-%m-%dT%H:%M:%S.%f".
58
+ user_identity (str): the user identity in this format: username - firstname lastname - email .
59
+ activity_type (str): the user activity type.
60
+
61
+ Returns:
62
+ List[Log]: a list of logs
63
+
64
+ Example:
65
+ >>> from geobox import Geobox
66
+ >>> from geopox.log import Log
67
+ >>> client = GeoboxClient()
68
+ >>> logs = Log.get_logs(client)
69
+ or
70
+ >>> logs = client.get_logs()
71
+ """
72
+ params = {
73
+ 'search': kwargs.get('search'),
74
+ 'order_by': kwargs.get('order_by'),
75
+ 'skip': kwargs.get('skip'),
76
+ 'limit': kwargs.get('limit'),
77
+ 'user_id': kwargs.get('user_id'),
78
+ 'from_date': kwargs.get('from_date').strftime("%Y-%m-%dT%H:%M:%S.%f") if kwargs.get('from_date') else None,
79
+ 'to_date': kwargs.get('to_date').strftime("%Y-%m-%dT%H:%M:%S.%f") if kwargs.get('to_date') else None,
80
+ 'user_identity': kwargs.get('user_identity'),
81
+ 'activity_type': kwargs.get('activity_type')
82
+ }
83
+ return super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: Log(api, item['id'], item))
84
+
85
+
86
+ def delete(self) -> None:
87
+ """
88
+ Delete a log (privileges required)
89
+
90
+ Returns:
91
+ None
92
+
93
+ Example:
94
+ >>> from geobox import Geobox
95
+ >>> from geopox.log import Log
96
+ >>> client = GeoboxClient()
97
+ >>> log = Log.get_logs(client)[0]
98
+ >>> log.delete()
99
+ """
100
+ super().delete(self.endpoint)
101
+ self.log_id = None