saviialib 1.6.1__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.
Files changed (91) hide show
  1. saviialib/__init__.py +79 -0
  2. saviialib/general_types/__init__.py +0 -0
  3. saviialib/general_types/api/__init__.py +0 -0
  4. saviialib/general_types/api/saviia_api_types.py +48 -0
  5. saviialib/general_types/api/saviia_backup_api_types.py +24 -0
  6. saviialib/general_types/api/saviia_netcamera_api_types.py +11 -0
  7. saviialib/general_types/api/saviia_shakes_api_types.py +21 -0
  8. saviialib/general_types/api/saviia_thies_api_types.py +31 -0
  9. saviialib/general_types/error_types/__init__.py +0 -0
  10. saviialib/general_types/error_types/api/__init__.py +0 -0
  11. saviialib/general_types/error_types/api/saviia_api_error_types.py +113 -0
  12. saviialib/general_types/error_types/api/saviia_netcamera_error_types.py +7 -0
  13. saviialib/general_types/error_types/common/__init__.py +7 -0
  14. saviialib/general_types/error_types/common/common_types.py +26 -0
  15. saviialib/libs/directory_client/__init__.py +4 -0
  16. saviialib/libs/directory_client/client/os_client.py +55 -0
  17. saviialib/libs/directory_client/directory_client.py +44 -0
  18. saviialib/libs/directory_client/directory_client_contract.py +40 -0
  19. saviialib/libs/directory_client/types/directory_client_types.py +6 -0
  20. saviialib/libs/files_client/__init__.py +4 -0
  21. saviialib/libs/files_client/clients/aiofiles_client.py +44 -0
  22. saviialib/libs/files_client/clients/csv_client.py +42 -0
  23. saviialib/libs/files_client/files_client.py +26 -0
  24. saviialib/libs/files_client/files_client_contract.py +13 -0
  25. saviialib/libs/files_client/types/files_client_types.py +32 -0
  26. saviialib/libs/ftp_client/__init__.py +4 -0
  27. saviialib/libs/ftp_client/clients/__init__.py +0 -0
  28. saviialib/libs/ftp_client/clients/aioftp_client.py +52 -0
  29. saviialib/libs/ftp_client/clients/ftplib_client.py +58 -0
  30. saviialib/libs/ftp_client/ftp_client.py +25 -0
  31. saviialib/libs/ftp_client/ftp_client_contract.py +13 -0
  32. saviialib/libs/ftp_client/types/__init__.py +3 -0
  33. saviialib/libs/ftp_client/types/ftp_client_types.py +18 -0
  34. saviialib/libs/log_client/__init__.py +19 -0
  35. saviialib/libs/log_client/log_client.py +46 -0
  36. saviialib/libs/log_client/log_client_contract.py +28 -0
  37. saviialib/libs/log_client/logging_client/logging_client.py +58 -0
  38. saviialib/libs/log_client/types/__init__.py +0 -0
  39. saviialib/libs/log_client/types/log_client_types.py +47 -0
  40. saviialib/libs/log_client/utils/log_client_utils.py +6 -0
  41. saviialib/libs/sftp_client/__init__.py +8 -0
  42. saviialib/libs/sftp_client/clients/asyncssh_sftp_client.py +83 -0
  43. saviialib/libs/sftp_client/sftp_client.py +26 -0
  44. saviialib/libs/sftp_client/sftp_client_contract.py +13 -0
  45. saviialib/libs/sftp_client/types/sftp_client_types.py +24 -0
  46. saviialib/libs/sharepoint_client/__init__.py +17 -0
  47. saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py +160 -0
  48. saviialib/libs/sharepoint_client/sharepoint_client.py +58 -0
  49. saviialib/libs/sharepoint_client/sharepoint_client_contract.py +26 -0
  50. saviialib/libs/sharepoint_client/types/sharepoint_client_types.py +30 -0
  51. saviialib/libs/zero_dependency/utils/booleans_utils.py +2 -0
  52. saviialib/libs/zero_dependency/utils/datetime_utils.py +25 -0
  53. saviialib/libs/zero_dependency/utils/strings_utils.py +5 -0
  54. saviialib/services/backup/__init__.py +0 -0
  55. saviialib/services/backup/api.py +36 -0
  56. saviialib/services/backup/controllers/__init__.py +0 -0
  57. saviialib/services/backup/controllers/types/__init__.py +6 -0
  58. saviialib/services/backup/controllers/types/upload_backup_to_sharepoint_types.py +18 -0
  59. saviialib/services/backup/controllers/upload_backup_to_sharepoint.py +87 -0
  60. saviialib/services/backup/use_cases/constants/upload_backup_to_sharepoint_constants.py +5 -0
  61. saviialib/services/backup/use_cases/types/__init__.py +7 -0
  62. saviialib/services/backup/use_cases/types/upload_backup_to_sharepoint_types.py +11 -0
  63. saviialib/services/backup/use_cases/upload_backup_to_sharepoint.py +474 -0
  64. saviialib/services/backup/utils/__init__.py +3 -0
  65. saviialib/services/backup/utils/upload_backup_to_sharepoint_utils.py +100 -0
  66. saviialib/services/netcamera/api.py +30 -0
  67. saviialib/services/netcamera/controllers/get_media_files.py +40 -0
  68. saviialib/services/netcamera/controllers/types/get_media_files_types.py +16 -0
  69. saviialib/services/netcamera/use_cases/get_media_files.py +76 -0
  70. saviialib/services/netcamera/use_cases/types/get_media_files_types.py +18 -0
  71. saviialib/services/shakes/__init__.py +0 -0
  72. saviialib/services/shakes/api.py +31 -0
  73. saviialib/services/shakes/controllers/get_miniseed_files.py +48 -0
  74. saviialib/services/shakes/controllers/types/get_miniseed_files_types.py +16 -0
  75. saviialib/services/shakes/use_cases/get_miniseed_files.py +79 -0
  76. saviialib/services/shakes/use_cases/types/get_miniseed_files_types.py +18 -0
  77. saviialib/services/shakes/use_cases/utils/get_miniseed_files_utils.py +11 -0
  78. saviialib/services/thies/__init__.py +0 -0
  79. saviialib/services/thies/api.py +42 -0
  80. saviialib/services/thies/constants/update_thies_data_constants.py +67 -0
  81. saviialib/services/thies/controllers/types/update_thies_data_types.py +18 -0
  82. saviialib/services/thies/controllers/update_thies_data.py +119 -0
  83. saviialib/services/thies/use_cases/components/create_thies_statistics_file.py +115 -0
  84. saviialib/services/thies/use_cases/components/thies_bp.py +442 -0
  85. saviialib/services/thies/use_cases/types/update_thies_data_types.py +24 -0
  86. saviialib/services/thies/use_cases/update_thies_data.py +391 -0
  87. saviialib/services/thies/utils/update_thies_data_utils.py +21 -0
  88. saviialib-1.6.1.dist-info/METADATA +126 -0
  89. saviialib-1.6.1.dist-info/RECORD +91 -0
  90. saviialib-1.6.1.dist-info/WHEEL +4 -0
  91. saviialib-1.6.1.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,391 @@
1
+ from saviialib.general_types.error_types.api.saviia_api_error_types import (
2
+ SharePointFetchingError,
3
+ SharePointDirectoryError,
4
+ SharePointUploadError,
5
+ ThiesConnectionError,
6
+ ThiesFetchingError,
7
+ )
8
+ from saviialib.general_types.error_types.common import (
9
+ EmptyDataError,
10
+ FtpClientError,
11
+ SharepointClientError,
12
+ )
13
+ from saviialib.libs.ftp_client import (
14
+ FTPClient,
15
+ FtpClientInitArgs,
16
+ FtpListFilesArgs,
17
+ FtpReadFileArgs,
18
+ )
19
+ from saviialib.libs.sharepoint_client import (
20
+ SharepointClient,
21
+ SharepointClientInitArgs,
22
+ SpListFilesArgs,
23
+ SpListFoldersArgs,
24
+ SpUploadFileArgs,
25
+ )
26
+ from saviialib.libs.directory_client import DirectoryClient, DirectoryClientArgs
27
+
28
+ from saviialib.libs.files_client import (
29
+ FilesClient,
30
+ FilesClientInitArgs,
31
+ WriteArgs,
32
+ ReadArgs,
33
+ )
34
+ from saviialib.services.backup.use_cases.types import (
35
+ FtpClientConfig,
36
+ SharepointConfig,
37
+ UpdateThiesDataUseCaseInput,
38
+ )
39
+ from saviialib.services.backup.utils import (
40
+ parse_execute_response,
41
+ )
42
+ from saviialib.libs.zero_dependency.utils.datetime_utils import today, datetime_to_str
43
+ from .components.create_thies_statistics_file import create_thies_daily_statistics_file
44
+ from typing import Set, Dict, List
45
+
46
+
47
+ class UpdateThiesDataUseCase:
48
+ BASE_FOLDER_NAME = "thies"
49
+
50
+ def __init__(self, input: UpdateThiesDataUseCaseInput):
51
+ self.sharepoint_client = self._initialize_sharepoint_client(
52
+ input.sharepoint_config
53
+ )
54
+ self.logger = input.logger
55
+ self.thies_ftp_client = self._initialize_thies_ftp_client(input.ftp_config)
56
+ self.sharepoint_folders_path = input.sharepoint_folders_path
57
+ self.ftp_server_folders_path = input.ftp_server_folders_path
58
+ self.local_backup_path = input.local_backup_source_path
59
+ self.sharepoint_base_url = f"/sites/{self.sharepoint_client.site_name}"
60
+ self.uploading = set()
61
+ self.os_client = self._initialize_os_client()
62
+ self.files_client = self._initialize_files_client()
63
+
64
+ def _initialize_sharepoint_client(
65
+ self, config: SharepointConfig
66
+ ) -> SharepointClient:
67
+ """Initialize the HTTP client."""
68
+ try:
69
+ return SharepointClient(
70
+ SharepointClientInitArgs(config, client_name="sharepoint_rest_api")
71
+ )
72
+ except ConnectionError as error:
73
+ raise SharepointClientError(error)
74
+
75
+ def _initialize_thies_ftp_client(self, config: FtpClientConfig) -> FTPClient:
76
+ """Initialize the FTP client."""
77
+ try:
78
+ return FTPClient(FtpClientInitArgs(config, client_name="ftplib_client"))
79
+ except RuntimeError as error:
80
+ raise FtpClientError(error)
81
+
82
+ def _initialize_os_client(self) -> DirectoryClient:
83
+ return DirectoryClient(DirectoryClientArgs(client_name="os_client"))
84
+
85
+ def _initialize_files_client(self) -> FilesClient:
86
+ return FilesClient(FilesClientInitArgs(client_name="aiofiles_client"))
87
+
88
+ async def _validate_sharepoint_current_folders(self):
89
+ async with self.sharepoint_client:
90
+ folder_base_path = "/".join(
91
+ self.sharepoint_folders_path[0].split("/")[0:-1]
92
+ )
93
+ relative_url = f"{self.sharepoint_base_url}/{folder_base_path}"
94
+ response = await self.sharepoint_client.list_folders(
95
+ SpListFoldersArgs(relative_url)
96
+ )
97
+
98
+ current_folders = [item["Name"] for item in response["value"]] # type: ignore
99
+
100
+ for folder_path in self.sharepoint_folders_path:
101
+ folder_name = folder_path.split("/")[-1]
102
+ if folder_name not in current_folders:
103
+ raise SharePointDirectoryError(
104
+ reason=f"The current folder '{folder_name}' doesn't exist."
105
+ )
106
+
107
+ async def fetch_cloud_file_names(self) -> Set[str]:
108
+ """Fetch file names from the RCER cloud."""
109
+ await self._validate_sharepoint_current_folders()
110
+ try:
111
+ cloud_files = set()
112
+ async with self.sharepoint_client:
113
+ for folder_path in self.sharepoint_folders_path:
114
+ folder_name = folder_path.split("/")[-1]
115
+ relative_url = f"{self.sharepoint_base_url}/{folder_path}"
116
+ args = SpListFilesArgs(folder_relative_url=relative_url)
117
+ response = await self.sharepoint_client.list_files(args)
118
+ cloud_files.update(
119
+ {
120
+ (f"{folder_name}_{item['Name']}", int(item["Length"]))
121
+ for item in response["value"] # type: ignore
122
+ } # type: ignore
123
+ )
124
+ return cloud_files
125
+ except Exception as error:
126
+ raise SharePointFetchingError(reason=error)
127
+
128
+ async def fetch_thies_file_names(self) -> Set[str]:
129
+ """Fetch file names from the THIES FTP server."""
130
+ try:
131
+ thies_files = set()
132
+ for folder_path in self.ftp_server_folders_path:
133
+ # AV for average, and EXT for extreme.
134
+ prefix = "AVG" if "AV" in folder_path else "EXT"
135
+ files = await self.thies_ftp_client.list_files(
136
+ FtpListFilesArgs(path=folder_path)
137
+ )
138
+ files_names = {(f"{prefix}_{name}", size) for name, size in files}
139
+ thies_files.update(files_names)
140
+ return thies_files
141
+ except ConnectionRefusedError as error:
142
+ raise ThiesConnectionError(reason=error)
143
+ except ConnectionAbortedError as error:
144
+ raise ThiesFetchingError(reason=error)
145
+
146
+ async def fetch_thies_file_content(self) -> Dict[str, bytes]:
147
+ """Fetch the content of files from the THIES FTP server."""
148
+ try:
149
+ content_files = {}
150
+ for file in self.uploading:
151
+ prefix, filename = file.split("_", 1)
152
+ file_path = self.os_client.join_paths(
153
+ self.local_backup_path,
154
+ UpdateThiesDataUseCase.BASE_FOLDER_NAME,
155
+ prefix,
156
+ filename,
157
+ )
158
+ content = await self.files_client.read(
159
+ ReadArgs(file_path=file_path, mode="rb")
160
+ )
161
+ self.logger.debug(
162
+ "[thies_synchronization_lib] Fetching file '%s' from '%s'.",
163
+ file,
164
+ file_path,
165
+ )
166
+ # Save file content with its original name.
167
+ content_files[file] = content
168
+ return content_files
169
+ except ConnectionRefusedError as error:
170
+ raise ThiesConnectionError(reason=error)
171
+ except ConnectionAbortedError as error:
172
+ raise ThiesFetchingError(reason=error)
173
+
174
+ async def upload_thies_files_to_sharepoint(
175
+ self, files: Dict
176
+ ) -> Dict[str, List[str]]:
177
+ """Upload files to SharePoint and categorize the results."""
178
+ upload_results = {"failed_files": [], "new_files": []}
179
+
180
+ async with self.sharepoint_client:
181
+ for file, file_content in files.items():
182
+ try:
183
+ origin, file_name = file.split("_", 1)
184
+ # Check if the first folder is for AVG, otherwise assume it's for EXT
185
+ if "AVG" in self.sharepoint_folders_path[0]:
186
+ avg_folder = self.sharepoint_folders_path[0]
187
+ ext_folder = self.sharepoint_folders_path[1]
188
+ else:
189
+ avg_folder = self.sharepoint_folders_path[1]
190
+ ext_folder = self.sharepoint_folders_path[0]
191
+ folder_path = avg_folder if origin == "AVG" else ext_folder
192
+ relative_url = f"{self.sharepoint_base_url}/{folder_path}"
193
+ await self.sharepoint_client.upload_file(
194
+ SpUploadFileArgs(
195
+ folder_relative_url=relative_url,
196
+ file_content=file_content,
197
+ file_name=file_name,
198
+ )
199
+ )
200
+ upload_results["new_files"].append(file)
201
+ self.logger.debug(
202
+ "[thies_synchronization_lib] File '%s' uploaded successfully to '%s' ✅",
203
+ file_name,
204
+ relative_url,
205
+ )
206
+
207
+ except ConnectionError as error:
208
+ self.logger.error(
209
+ "[thies_synchronization_lib] Unexpected error from with file '%s'",
210
+ file_name,
211
+ )
212
+ upload_results["failed_files"].append(
213
+ f"{file} (Error: {str(error)})"
214
+ )
215
+
216
+ if upload_results["failed_files"]:
217
+ raise SharePointUploadError(
218
+ reason="Files failed to upload: "
219
+ + ", ".join(upload_results["failed_files"])
220
+ )
221
+
222
+ return upload_results
223
+
224
+ async def _sync_pending_files(self, thies_files: set, cloud_files: set) -> set:
225
+ thies_files_dict = {name: size for name, size in thies_files}
226
+ cloud_files_dict = {name: size for name, size in cloud_files}
227
+ uploading = set()
228
+ for f_from_thies, f_size_from_thies in thies_files_dict.items():
229
+ # If is in thies but not in cloud, then upload it
230
+ if f_from_thies not in cloud_files_dict:
231
+ uploading.add(f_from_thies)
232
+ else:
233
+ # If the file is in both services, but the size is different, then upload it
234
+ f_size_from_cloud = cloud_files_dict[f_from_thies]
235
+ if f_size_from_thies != f_size_from_cloud:
236
+ uploading.add(f_from_thies)
237
+ return uploading
238
+
239
+ async def _extract_thies_daily_statistics(self) -> None:
240
+ # Read the daily files and save each data in the folder
241
+ daily_files = [
242
+ prefix + datetime_to_str(today(), date_format="%Y%m%d") + ".BIN"
243
+ for prefix in ["AVG_", "EXT_"]
244
+ ]
245
+ # Receive from FTP server and write the file in thies-daily-files
246
+ for file in daily_files:
247
+ prefix, filename = file.split("_", 1)
248
+ # The first path is for AVG files. The second file is for EXT files
249
+ file_path = self.os_client.join_paths(
250
+ self.local_backup_path, UpdateThiesDataUseCase.BASE_FOLDER_NAME, prefix
251
+ )
252
+ files = await self.os_client.listdir(file_path)
253
+ if filename not in files:
254
+ reason = "The file might not be available yet for statistics."
255
+ self.logger.warning("[thies_synchronization_lib] Warning: %s", reason)
256
+ self.logger.warning(
257
+ "[thies_synchronization_lib] Skipping the creation of daily statistics %s",
258
+ filename,
259
+ )
260
+ return
261
+ await create_thies_daily_statistics_file(
262
+ self.local_backup_path, self.os_client, self.logger
263
+ )
264
+
265
+ async def _validate_local_backup(self):
266
+ backup_path = self.os_client.join_paths(
267
+ self.local_backup_path, UpdateThiesDataUseCase.BASE_FOLDER_NAME
268
+ )
269
+ backup_dir_exists = await self.os_client.path_exists(backup_path)
270
+ if not backup_dir_exists:
271
+ await self.os_client.makedirs(backup_path)
272
+
273
+ for dest_folder in {"AVG", "EXT"}:
274
+ dest_folder_path = self.os_client.join_paths(backup_path, dest_folder)
275
+ dest_folder_exists = await self.os_client.path_exists(dest_folder_path)
276
+ if not dest_folder_exists:
277
+ await self.os_client.makedirs(
278
+ self.os_client.join_paths(backup_path, dest_folder)
279
+ )
280
+
281
+ async def _fill_local_backup(self, thies_files: Set[str]) -> None:
282
+ local_avg_files = await self.os_client.listdir(
283
+ self.os_client.join_paths(
284
+ self.local_backup_path, UpdateThiesDataUseCase.BASE_FOLDER_NAME, "AVG"
285
+ ),
286
+ more_info=True,
287
+ )
288
+ local_avg_files = {filename: size for filename, size in local_avg_files}
289
+ local_ext_files = await self.os_client.listdir(
290
+ self.os_client.join_paths(
291
+ self.local_backup_path, UpdateThiesDataUseCase.BASE_FOLDER_NAME, "EXT"
292
+ ),
293
+ more_info=True,
294
+ )
295
+ local_ext_files = {filename: size for filename, size in local_ext_files}
296
+ try:
297
+ for file, orig_size in thies_files:
298
+ prefix, filename = file.split("_", 1)
299
+ # The first path is for AVG files. The second file is for EXT files
300
+ folder_path = next(
301
+ (
302
+ path
303
+ for path in self.ftp_server_folders_path
304
+ if prefix == ("AVG" if "AV" in path else "EXT")
305
+ ),
306
+ self.ftp_server_folders_path[0], # Default to the first path
307
+ )
308
+ dest_path = self.os_client.join_paths(
309
+ self.local_backup_path,
310
+ UpdateThiesDataUseCase.BASE_FOLDER_NAME,
311
+ prefix,
312
+ )
313
+ new_size = (
314
+ local_avg_files.get(filename, None)
315
+ if prefix == "AVG"
316
+ else local_ext_files.get(filename, None)
317
+ )
318
+ should_be_added = False
319
+ if new_size and new_size != orig_size:
320
+ should_be_added = True
321
+ elif not new_size:
322
+ should_be_added = True
323
+ else:
324
+ # Should not be added, because it has the same size and it's saved in the local dir.
325
+ pass
326
+
327
+ if not should_be_added:
328
+ continue
329
+
330
+ self.logger.debug(
331
+ f"[thies_synchronization_lib] Saving {filename} in Thies local backup"
332
+ )
333
+ file_path = f"{folder_path}/{filename}"
334
+ file_content = await self.thies_ftp_client.read_file(
335
+ FtpReadFileArgs(file_path)
336
+ )
337
+ await self.files_client.write(
338
+ WriteArgs(
339
+ file_name=filename,
340
+ file_content=file_content,
341
+ mode="wb",
342
+ destination_path=dest_path,
343
+ )
344
+ )
345
+ except ConnectionRefusedError as error:
346
+ raise ThiesConnectionError(reason=error)
347
+ except ConnectionAbortedError as error:
348
+ raise ThiesFetchingError(reason=error)
349
+
350
+ async def execute(self):
351
+ """Synchronize data from the THIES Center to the cloud."""
352
+ self.logger.debug("[thies_synchronization_lib] Starting ...")
353
+ await self._validate_local_backup()
354
+ try:
355
+ thies_files = await self.fetch_thies_file_names()
356
+ await self._fill_local_backup(thies_files)
357
+ except RuntimeError as error:
358
+ raise FtpClientError(error)
359
+ self.logger.debug(
360
+ "[thies_synchronization_lib] Total files fetched from THIES: %s",
361
+ str(len(thies_files)),
362
+ )
363
+ try:
364
+ cloud_files = await self.fetch_cloud_file_names()
365
+ except RuntimeError as error:
366
+ raise SharepointClient(error) # type: ignore
367
+ self.logger.debug(
368
+ "[thies_synchronization_lib] Total files fetched from Sharepoint: %s",
369
+ str(len(cloud_files)),
370
+ )
371
+ self.uploading = await self._sync_pending_files(thies_files, cloud_files)
372
+ # Extract thies statistics for SAVIIA Sensors
373
+ await self._extract_thies_daily_statistics()
374
+ if not self.uploading:
375
+ raise EmptyDataError(reason="No files to upload.")
376
+ self.logger.debug(
377
+ "[thies_synchronization_lib] Total new files to upload: %s",
378
+ str(len(self.uploading)),
379
+ )
380
+ # Fetch the content of the files to be uploaded from THIES FTP Server
381
+ thies_fetched_files = await self.fetch_thies_file_content()
382
+ # # Upload the fetched files to SharePoint and gather statistics
383
+ upload_statistics = await self.upload_thies_files_to_sharepoint(
384
+ thies_fetched_files
385
+ )
386
+ self.logger.info(upload_statistics)
387
+ self.logger.debug(
388
+ "[thies_synchronization_lib] All the files were uploaded successfully 🎉"
389
+ )
390
+
391
+ return parse_execute_response(thies_fetched_files, upload_statistics) # type: ignore
@@ -0,0 +1,21 @@
1
+ from typing import Any
2
+
3
+ from saviialib.libs.zero_dependency.utils.datetime_utils import (
4
+ datetime_to_str,
5
+ today,
6
+ )
7
+
8
+
9
+ def parse_execute_response(
10
+ thies_fetched_files: dict[str, Any], upload_statistics: dict[str, Any]
11
+ ) -> dict[str, dict[str, int | str]]:
12
+ return {
13
+ **upload_statistics,
14
+ "processed_files": {
15
+ filename: {
16
+ "file_size": len(data),
17
+ "processed_date": datetime_to_str(today()),
18
+ }
19
+ for filename, data in thies_fetched_files.items()
20
+ },
21
+ }
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: saviialib
3
+ Version: 1.6.1
4
+ Summary: A client library for IoT projects in the RCER initiative
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: pedropablozavalat
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Dist: aiofiles
16
+ Requires-Dist: aioftp
17
+ Requires-Dist: aiohttp
18
+ Requires-Dist: asyncssh (==2.21.1)
19
+ Requires-Dist: bitarray
20
+ Requires-Dist: build
21
+ Requires-Dist: certifi
22
+ Requires-Dist: dotenv (>=0.9.9,<0.10.0)
23
+ Requires-Dist: ffmpeg-asyncio (==0.1.3)
24
+ Requires-Dist: numpy (>=2.2.0,<2.4.0)
25
+ Requires-Dist: pandas (>=2.2.3,<2.3.1)
26
+ Description-Content-Type: text/markdown
27
+
28
+ # SAVIIA Library
29
+ *Sistema de Administración y Visualización de Información para la Investigación y Análisis*
30
+
31
+ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/pedrozavalat/saviia-lib?style=for-the-badge)](https://github.com/pedrozavalat/saviia-lib/releases)
32
+
33
+
34
+ ## Installation
35
+ This library is designed for use with the SAVIIA Home Assistant Integration. It provides an API to retrieve files from a THIES Data Logger via an FTP server and upload them to a Microsoft SharePoint folder using the SharePoint REST API.
36
+
37
+ ```bash
38
+ pip install saviialib
39
+ ```
40
+
41
+ ## Saviia API Client Usage
42
+
43
+ ### Initialize the Saviia API Client
44
+ Import the necessary classes from the library.
45
+ ```python
46
+ from saviialib import SaviiaAPI, SaviiaAPIConfig
47
+ ```
48
+
49
+ To start using the library, you need to create an `SaviiaAPI` client instance with its configuration class `SaviiaAPIConfig`. Provide the required parameters such as FTP server details and SharePoint credentials:
50
+ ```python
51
+ config = SaviiaAPIConfig(
52
+ ftp_port=FTP_PORT,
53
+ ftp_host=FTP_HOST,
54
+ ftp_user=FTP_USER,
55
+ ftp_password=FTP_PASSWORD,
56
+ sharepoint_client_id=SHAREPOINT_CLIENT_ID,
57
+ sharepoint_client_secret=SHAREPOINT_CLIENT_SECRET,
58
+ sharepoint_tenant_id=SHAREPOINT_TENANT_ID,
59
+ sharepoint_tenant_name=SHAREPOINT_TENANT_NAME,
60
+ sharepoint_site_name=SHAREPOINT_SITE_NAME
61
+ )
62
+ ```
63
+ ```python
64
+ api_client = SaviiaAPI(config)
65
+ ```
66
+ **Notes:**
67
+ - Store sensitive data like `FTP_PASSWORD`, `FTP_USER`, and SharePoint credentials securely. Use environment variables or a secrets management tool to avoid hardcoding sensitive information in your codebase.
68
+
69
+ ### Access THIES Data Logger Services
70
+ To interact with the THIES Data Logger services, you can access the `thies` attribute of the `SaviiaAPI` instance:
71
+ ```python
72
+ thies_client = api_client.get('thies')
73
+ ```
74
+ This instance provides methods to interact with the THIES Data Logger. Currently, it includes the main method for extracting files from the FTP server and uploading them to SharePoint.
75
+
76
+ #### THIES files extraction and synchronization
77
+ The library provides a method to extract and synchronize THIES Data Logger files with the Microsoft SharePoint client. This method downloads files from the FTP server and uploads them to the specified SharePoint folder:
78
+ ```python
79
+ import asyncio
80
+ async def main():
81
+ # Before calling this method, you must have initialised the THIES service class ...
82
+ response = await thies_client.update_thies_data()
83
+ return response
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ ### Access Backup Services
89
+ To interact with the Backup services, you can access the `backup` attribute of the `SaviiaAPI` instance:
90
+ ```python
91
+ backup_client = api_client.get('backup')
92
+ ```
93
+ This instance provides methods to interact with the Backup services. Currently, it includes the main method for creating backups of specified directories in a local folder from Home Assistant environment. Then each backup file is uploaded to a Microsoft SharePoint folder.
94
+
95
+ #### Create Backup
96
+ The library provides a method which creates a backup of a specified directory in a local folder from Home Assistant environment. Then each backup file is uploaded to a Microsoft SharePoint folder:
97
+
98
+ ```python
99
+ import asyncio
100
+ async def main():
101
+ # Before calling this method, you must have initialised the Backup service class ...
102
+ response = await backup_client.upload_backup_to_sharepoint(
103
+ local_backup_path=LOCAL_BACKUP_PATH,
104
+ sharepoint_folder_path=SHAREPOINT_FOLDER_PATH
105
+ )
106
+ return response
107
+ asyncio.run(main())
108
+ ```
109
+ **Notes:**
110
+ - Ensure that the `local_backup_path` exists and contains the files you want to back up. It is a relative path from the Home Assistant configuration directory.
111
+ - The `sharepoint_folder_path` should be the path to the folder in SharePoint where you want to upload the backup files. For example, if your url is `https://yourtenant.sharepoint.com/sites/yoursite/Shared Documents/Backups`, the folder path would be `sites/yoursite/Shared Documents/Backups`.
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+ ## Contributing
120
+ If you're interested in contributing to this project, please follow the contributing guidelines. By contributing to this project, you agree to abide by its terms.
121
+ Contributions are welcome and appreciated!
122
+
123
+ ## License
124
+
125
+ `saviialib` was created by Pedro Pablo Zavala Tejos. It is licensed under the terms of the MIT license.
126
+
@@ -0,0 +1,91 @@
1
+ saviialib/__init__.py,sha256=pn4KYFwPsVe43mw1e0svWiqA0StzM2B5Cr0iPc481I0,3074
2
+ saviialib/general_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ saviialib/general_types/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ saviialib/general_types/api/saviia_api_types.py,sha256=kX45V3KQb48S1jEstXcAtbiemVUgemJE_XK5qP7BJm0,1318
5
+ saviialib/general_types/api/saviia_backup_api_types.py,sha256=mtXRxFjBOqfUBWWtgLZxNq-vb_mBkCUS9fxoFRJfoYE,791
6
+ saviialib/general_types/api/saviia_netcamera_api_types.py,sha256=WVA3VP_FiTGPhtTl8GCG2RAVo7YDS5TTyl-0I5rUK4Y,208
7
+ saviialib/general_types/api/saviia_shakes_api_types.py,sha256=XlLwfCyFeQXk9kH7H0EcUJHIigj9edFKgn3wKGJ1Z94,623
8
+ saviialib/general_types/api/saviia_thies_api_types.py,sha256=ueqOFjHvRuT-N8H1UZEVXErgK1S1iRw0AXeO8bTdBts,1017
9
+ saviialib/general_types/error_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ saviialib/general_types/error_types/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ saviialib/general_types/error_types/api/saviia_api_error_types.py,sha256=BcDhteS4KsFNCm3L9ZQJTQKs0bKRXq3LgM4mfxsW-HY,3270
12
+ saviialib/general_types/error_types/api/saviia_netcamera_error_types.py,sha256=ipdY8PDJOFLxjgUIWc09cuaiHzPtBvAb7YKoosTL0wY,246
13
+ saviialib/general_types/error_types/common/__init__.py,sha256=yOBLZbt64Ki9Q0IJ0tMAubgq7PtrQ7XQ3RgtAzyOjiE,170
14
+ saviialib/general_types/error_types/common/common_types.py,sha256=2w790TQNRLc4UskXNHAh8RMZIJLA8FYyQbVxkPp89UA,717
15
+ saviialib/libs/directory_client/__init__.py,sha256=ys07nzp74fHew2mUkbGpntp5w4t_PnhZIS6D4_mJw2A,162
16
+ saviialib/libs/directory_client/client/os_client.py,sha256=9TqkwUJvK08twe_5Mun-Q5h0FLrJ6SUg5JCJI60VhkU,1726
17
+ saviialib/libs/directory_client/directory_client.py,sha256=3CbKKtumYcZlIsH72MM0LZQVg_nn9ZQU8e03SExdyLA,1579
18
+ saviialib/libs/directory_client/directory_client_contract.py,sha256=k1smoQPKQwBlM0xeLKghNzz2s2T5Q1KcEwExqFOpaVA,871
19
+ saviialib/libs/directory_client/types/directory_client_types.py,sha256=ncMwVs_o6EYMuypXXmVInsjVDKJsdxVkmwj1M-LEInA,109
20
+ saviialib/libs/files_client/__init__.py,sha256=sIi9ne7Z3EfxnqGTaSmH-cZ8QsKyu0hoOz61GyA3njs,192
21
+ saviialib/libs/files_client/clients/aiofiles_client.py,sha256=nchUXaIzlpgDu0WNc3fsc0XS8o-tsTbiczBGvw0qgzQ,1633
22
+ saviialib/libs/files_client/clients/csv_client.py,sha256=Rk4QbiKlVKrYxYtxQt-Pmkp9QoB_dNgs5r36JB9hU0o,1478
23
+ saviialib/libs/files_client/files_client.py,sha256=oWfaMLA_Lw91H1_pPirFtFbdJh4abSyZp91qBsAiePs,950
24
+ saviialib/libs/files_client/files_client_contract.py,sha256=fYvd68IMpc1OFkxbzNSmRUoTWVctf3hkNVQ7QZV0m6I,304
25
+ saviialib/libs/files_client/types/files_client_types.py,sha256=z3ROStCjO-RtIqkoU8cCGYiaIYm8rxDheI5C0Vyl9lk,771
26
+ saviialib/libs/ftp_client/__init__.py,sha256=dW2Yutgc7mJJJzgKLhWKXMgQ6KIWJYfFa1sGpjHH5xU,191
27
+ saviialib/libs/ftp_client/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ saviialib/libs/ftp_client/clients/aioftp_client.py,sha256=eoTmf2y0i19G3f-jET7E_P9T2N_LF6CI0tAFfHbPMWM,1966
29
+ saviialib/libs/ftp_client/clients/ftplib_client.py,sha256=VcpAn23r03iyCUAj35xHU_AVNrll_45uHFN8Z9OnBkE,2203
30
+ saviialib/libs/ftp_client/ftp_client.py,sha256=Naj9p0yYWlXt9um0zKfvuHSoycM8JZg2SPBm8110pYk,1008
31
+ saviialib/libs/ftp_client/ftp_client_contract.py,sha256=tymkugDzsJ5PzUXIaSkwX1h7T0naR15qAkjrqswqDyM,338
32
+ saviialib/libs/ftp_client/types/__init__.py,sha256=syfwf9feP4QK7fkCTfl4j8l11ic-jHtfi1DE2chaWbs,155
33
+ saviialib/libs/ftp_client/types/ftp_client_types.py,sha256=e4SmYkewldulaD8ms2q75zVgLFXyBxBqoa_L-IQOmso,256
34
+ saviialib/libs/log_client/__init__.py,sha256=2v6-pE7qwWLo7PjyysMLPKCDfpMOAtEqRGq_tw9Qlcs,333
35
+ saviialib/libs/log_client/log_client.py,sha256=C_M-vlSQJmssIVNtco6KV9E2OXLdogP9ibDwuGJQw-I,1332
36
+ saviialib/libs/log_client/log_client_contract.py,sha256=DmU-TLz4onH7LWM8tVu_HTp31Cx4WwOvPShF3rizoqE,524
37
+ saviialib/libs/log_client/logging_client/logging_client.py,sha256=HkiKjEe_-mVfPmJYm3xI7ayKEa_4AiSXDJRqnyKijB0,1980
38
+ saviialib/libs/log_client/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
+ saviialib/libs/log_client/types/log_client_types.py,sha256=fV8-uVbCOf4IqxR3IUKQiK_k5nbT_r3KWyuwhkBaSc4,1118
40
+ saviialib/libs/log_client/utils/log_client_utils.py,sha256=NpFt_LvMvVSVswmoleMycjL6n1OlyfqDfWNs9SmKJfY,232
41
+ saviialib/libs/sftp_client/__init__.py,sha256=f51sFOd_XIhhd6x6AZ3ZdbJ86hO1xnqAk9MmEjwRJ-c,229
42
+ saviialib/libs/sftp_client/clients/asyncssh_sftp_client.py,sha256=vqCKHIx3K6QFujQP5DIAB5EYOz1zAdhi825LTRvhG20,3469
43
+ saviialib/libs/sftp_client/sftp_client.py,sha256=_5HRYSBPadxZEdHuHSpQ_VrwerBfu24izNRg-pvLzlU,918
44
+ saviialib/libs/sftp_client/sftp_client_contract.py,sha256=JWWxIfNBXcsBd8z3gQHGpeRASRjtP9WGkznoMfP0IZc,365
45
+ saviialib/libs/sftp_client/types/sftp_client_types.py,sha256=NzUZVq6yeGKkO7KF-v4Cc-rwaWeOgFCUGVPL1YBbYgA,446
46
+ saviialib/libs/sharepoint_client/__init__.py,sha256=6RbEzFtCfMrrY8F0JOCVcK6Gqt1FBJ09m0NuWHMCmCI,384
47
+ saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py,sha256=I3OfZ5M4uR86hLIBOlOQzGNyTzQRy1yckrLF1ne7F1g,6319
48
+ saviialib/libs/sharepoint_client/sharepoint_client.py,sha256=gXFisWWCk6pKhoEeaNN8dcHA5ywrGDLqfiKjchrHRy0,1816
49
+ saviialib/libs/sharepoint_client/sharepoint_client_contract.py,sha256=H-WsXR5f50jy5RRYNlgV61y_YGjOUq4U_E1DWUUrDYw,612
50
+ saviialib/libs/sharepoint_client/types/sharepoint_client_types.py,sha256=JruUCn6o16w00t4zxDrxmv_Bxa52UyShGYmwUFfBCCQ,482
51
+ saviialib/libs/zero_dependency/utils/booleans_utils.py,sha256=Wc68B8TXMy_bCDRBlpbIimkcheyk-CBm8Ph6LyjV87c,83
52
+ saviialib/libs/zero_dependency/utils/datetime_utils.py,sha256=c2H_JpUKpunCzDw6I4alDWNftEqaciWGtEUklPepm_s,759
53
+ saviialib/libs/zero_dependency/utils/strings_utils.py,sha256=XqEukvCLkQ0PhjjPm9FTjrzbwBYMejs-0uSoLwSxhPo,133
54
+ saviialib/services/backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
+ saviialib/services/backup/api.py,sha256=S1dMGyyjYzVyo-E8FMttHZMOO2TtQoAMaLp-oquW46g,1408
56
+ saviialib/services/backup/controllers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
+ saviialib/services/backup/controllers/types/__init__.py,sha256=0zIWXQb6YQVIIbb5b_FeL-D9Zy_d1GqR8kVz3hen4IU,222
58
+ saviialib/services/backup/controllers/types/upload_backup_to_sharepoint_types.py,sha256=AzoHx3AWrVCM8LjmlEovDlRe4kJPJBbRoMToG8QK_Nw,464
59
+ saviialib/services/backup/controllers/upload_backup_to_sharepoint.py,sha256=rW8FmcalRO1Ip8rhOsfpJIKRVYyn8oO-_jJSiS7MJOk,3827
60
+ saviialib/services/backup/use_cases/constants/upload_backup_to_sharepoint_constants.py,sha256=erkn-3E8YwBMFs25o7exXoK7s73NdgP9IYDXeWzALcI,98
61
+ saviialib/services/backup/use_cases/types/__init__.py,sha256=uVfu-L3-2QGnki0-rL2fOVTj4hVMvykyqKEv7kQHNvE,224
62
+ saviialib/services/backup/use_cases/types/upload_backup_to_sharepoint_types.py,sha256=QSZ32Fsan-jGpmGFpip86N3S16oWaeIxri5at2x46nw,322
63
+ saviialib/services/backup/use_cases/upload_backup_to_sharepoint.py,sha256=ZRzjwWncwgcB5_eNMsGBO37HVKMpkgWC0c_teaOrlos,18967
64
+ saviialib/services/backup/utils/__init__.py,sha256=Khz07TAC17xGBbkSgNcOXSjzeFm6-NiQadNJ3R7tRgg,112
65
+ saviialib/services/backup/utils/upload_backup_to_sharepoint_utils.py,sha256=oNZhzEkNooHGKs8eiPOmgh3I847favH1JjD3ROkSP2E,3258
66
+ saviialib/services/netcamera/api.py,sha256=W8ZKfh_iGEvfE36yGXYH9RlveUUtHoxQI_E3__YGfq0,1182
67
+ saviialib/services/netcamera/controllers/get_media_files.py,sha256=CEfZ7YRhXBcuGeK3HWB3Bpp-i-pIvAs70krreCHL_eA,1507
68
+ saviialib/services/netcamera/controllers/types/get_media_files_types.py,sha256=40V0ARqtloRM16KYYxzeq5aQU-mrghEK7FfkeFoSmbk,417
69
+ saviialib/services/netcamera/use_cases/get_media_files.py,sha256=ct_MOOgxzqCURQo2JJx1ioBaO-Qq-Gj-27Y4JAdZbeo,2801
70
+ saviialib/services/netcamera/use_cases/types/get_media_files_types.py,sha256=XCpodI-WdZYbhd3DD1LkKDgLTSRKlXkHClbhwT8zXm8,333
71
+ saviialib/services/shakes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
+ saviialib/services/shakes/api.py,sha256=WvmOJ35lPdJuqSEl9-q9VOGNxqkqvzfBUmkp-G8WKj4,1230
73
+ saviialib/services/shakes/controllers/get_miniseed_files.py,sha256=k55QiIQoNHyMQsgW939IbrPY4tHy8cFMg51LDVQQSgs,1934
74
+ saviialib/services/shakes/controllers/types/get_miniseed_files_types.py,sha256=LZ8LZxpjPYrl-EIuwQixXl8z4Ygl81DgodoHyxzvIxY,366
75
+ saviialib/services/shakes/use_cases/get_miniseed_files.py,sha256=HD6VphToOF8eq-J281qOg9AgeD6hFAsVfScwvP5c9jc,3033
76
+ saviialib/services/shakes/use_cases/types/get_miniseed_files_types.py,sha256=bb5XurP08pHhhNK80dvgoSP_OmHFEdKhT-jzyd8w5kk,385
77
+ saviialib/services/shakes/use_cases/utils/get_miniseed_files_utils.py,sha256=6TEdu9MglnM0_FTuxs9BETneXqQBUGb-Q1rDKhvRLvg,288
78
+ saviialib/services/thies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
+ saviialib/services/thies/api.py,sha256=jbKk1aMbGdQFEanQJnbouj7SCbwi2CtAwZVDeAXp26M,1646
80
+ saviialib/services/thies/constants/update_thies_data_constants.py,sha256=3t5zthZOT3mPKqZZqCXhO13sIkiAbYeyyXz8ZRGXrPo,1592
81
+ saviialib/services/thies/controllers/types/update_thies_data_types.py,sha256=95b7rq_VusvibsUKJj9EuKz1au0-n4ssew4LiBWWsB0,473
82
+ saviialib/services/thies/controllers/update_thies_data.py,sha256=UVvLz-Py6m4FVAbi7ZWs2yL6n8rKSuQiWTXaWK5QGHc,4963
83
+ saviialib/services/thies/use_cases/components/create_thies_statistics_file.py,sha256=uHz6XrRXUDrAbxpW1nnMaZ2Qu_Y3XTQblUAeZrgILM8,4531
84
+ saviialib/services/thies/use_cases/components/thies_bp.py,sha256=1Iq5Wz3kqzJfQNawV_v8ORr_Pl-PoamFp_2Xo8DjvmI,15042
85
+ saviialib/services/thies/use_cases/types/update_thies_data_types.py,sha256=oSisdBqCDU54QKbAE1IV4wjBjy02ynJdE4bbOLVgb0k,588
86
+ saviialib/services/thies/use_cases/update_thies_data.py,sha256=MfAA6lRyyAdyQwc6yiKL2SdiPKXZNoKdxMtmj1vFdio,16750
87
+ saviialib/services/thies/utils/update_thies_data_utils.py,sha256=EpjYWXqyHxJ-dO3MHhdXp-rGV7WyUckeFko-nnfnNac,555
88
+ saviialib-1.6.1.dist-info/METADATA,sha256=I_HZJiOP8ELt1MZMyuwFqc9y7GpzRA_18p3GnJ-Bpjg,5235
89
+ saviialib-1.6.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
90
+ saviialib-1.6.1.dist-info/licenses/LICENSE,sha256=NWpf6b38xgBWPBo5HZsCbdfp9hZSliEbRqWQgm0fkOo,1076
91
+ saviialib-1.6.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.2.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025, pedropablozavalat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+