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.
- saviialib/__init__.py +79 -0
- saviialib/general_types/__init__.py +0 -0
- saviialib/general_types/api/__init__.py +0 -0
- saviialib/general_types/api/saviia_api_types.py +48 -0
- saviialib/general_types/api/saviia_backup_api_types.py +24 -0
- saviialib/general_types/api/saviia_netcamera_api_types.py +11 -0
- saviialib/general_types/api/saviia_shakes_api_types.py +21 -0
- saviialib/general_types/api/saviia_thies_api_types.py +31 -0
- saviialib/general_types/error_types/__init__.py +0 -0
- saviialib/general_types/error_types/api/__init__.py +0 -0
- saviialib/general_types/error_types/api/saviia_api_error_types.py +113 -0
- saviialib/general_types/error_types/api/saviia_netcamera_error_types.py +7 -0
- saviialib/general_types/error_types/common/__init__.py +7 -0
- saviialib/general_types/error_types/common/common_types.py +26 -0
- saviialib/libs/directory_client/__init__.py +4 -0
- saviialib/libs/directory_client/client/os_client.py +55 -0
- saviialib/libs/directory_client/directory_client.py +44 -0
- saviialib/libs/directory_client/directory_client_contract.py +40 -0
- saviialib/libs/directory_client/types/directory_client_types.py +6 -0
- saviialib/libs/files_client/__init__.py +4 -0
- saviialib/libs/files_client/clients/aiofiles_client.py +44 -0
- saviialib/libs/files_client/clients/csv_client.py +42 -0
- saviialib/libs/files_client/files_client.py +26 -0
- saviialib/libs/files_client/files_client_contract.py +13 -0
- saviialib/libs/files_client/types/files_client_types.py +32 -0
- saviialib/libs/ftp_client/__init__.py +4 -0
- saviialib/libs/ftp_client/clients/__init__.py +0 -0
- saviialib/libs/ftp_client/clients/aioftp_client.py +52 -0
- saviialib/libs/ftp_client/clients/ftplib_client.py +58 -0
- saviialib/libs/ftp_client/ftp_client.py +25 -0
- saviialib/libs/ftp_client/ftp_client_contract.py +13 -0
- saviialib/libs/ftp_client/types/__init__.py +3 -0
- saviialib/libs/ftp_client/types/ftp_client_types.py +18 -0
- saviialib/libs/log_client/__init__.py +19 -0
- saviialib/libs/log_client/log_client.py +46 -0
- saviialib/libs/log_client/log_client_contract.py +28 -0
- saviialib/libs/log_client/logging_client/logging_client.py +58 -0
- saviialib/libs/log_client/types/__init__.py +0 -0
- saviialib/libs/log_client/types/log_client_types.py +47 -0
- saviialib/libs/log_client/utils/log_client_utils.py +6 -0
- saviialib/libs/sftp_client/__init__.py +8 -0
- saviialib/libs/sftp_client/clients/asyncssh_sftp_client.py +83 -0
- saviialib/libs/sftp_client/sftp_client.py +26 -0
- saviialib/libs/sftp_client/sftp_client_contract.py +13 -0
- saviialib/libs/sftp_client/types/sftp_client_types.py +24 -0
- saviialib/libs/sharepoint_client/__init__.py +17 -0
- saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py +160 -0
- saviialib/libs/sharepoint_client/sharepoint_client.py +58 -0
- saviialib/libs/sharepoint_client/sharepoint_client_contract.py +26 -0
- saviialib/libs/sharepoint_client/types/sharepoint_client_types.py +30 -0
- saviialib/libs/zero_dependency/utils/booleans_utils.py +2 -0
- saviialib/libs/zero_dependency/utils/datetime_utils.py +25 -0
- saviialib/libs/zero_dependency/utils/strings_utils.py +5 -0
- saviialib/services/backup/__init__.py +0 -0
- saviialib/services/backup/api.py +36 -0
- saviialib/services/backup/controllers/__init__.py +0 -0
- saviialib/services/backup/controllers/types/__init__.py +6 -0
- saviialib/services/backup/controllers/types/upload_backup_to_sharepoint_types.py +18 -0
- saviialib/services/backup/controllers/upload_backup_to_sharepoint.py +87 -0
- saviialib/services/backup/use_cases/constants/upload_backup_to_sharepoint_constants.py +5 -0
- saviialib/services/backup/use_cases/types/__init__.py +7 -0
- saviialib/services/backup/use_cases/types/upload_backup_to_sharepoint_types.py +11 -0
- saviialib/services/backup/use_cases/upload_backup_to_sharepoint.py +474 -0
- saviialib/services/backup/utils/__init__.py +3 -0
- saviialib/services/backup/utils/upload_backup_to_sharepoint_utils.py +100 -0
- saviialib/services/netcamera/api.py +30 -0
- saviialib/services/netcamera/controllers/get_media_files.py +40 -0
- saviialib/services/netcamera/controllers/types/get_media_files_types.py +16 -0
- saviialib/services/netcamera/use_cases/get_media_files.py +76 -0
- saviialib/services/netcamera/use_cases/types/get_media_files_types.py +18 -0
- saviialib/services/shakes/__init__.py +0 -0
- saviialib/services/shakes/api.py +31 -0
- saviialib/services/shakes/controllers/get_miniseed_files.py +48 -0
- saviialib/services/shakes/controllers/types/get_miniseed_files_types.py +16 -0
- saviialib/services/shakes/use_cases/get_miniseed_files.py +79 -0
- saviialib/services/shakes/use_cases/types/get_miniseed_files_types.py +18 -0
- saviialib/services/shakes/use_cases/utils/get_miniseed_files_utils.py +11 -0
- saviialib/services/thies/__init__.py +0 -0
- saviialib/services/thies/api.py +42 -0
- saviialib/services/thies/constants/update_thies_data_constants.py +67 -0
- saviialib/services/thies/controllers/types/update_thies_data_types.py +18 -0
- saviialib/services/thies/controllers/update_thies_data.py +119 -0
- saviialib/services/thies/use_cases/components/create_thies_statistics_file.py +115 -0
- saviialib/services/thies/use_cases/components/thies_bp.py +442 -0
- saviialib/services/thies/use_cases/types/update_thies_data_types.py +24 -0
- saviialib/services/thies/use_cases/update_thies_data.py +391 -0
- saviialib/services/thies/utils/update_thies_data_utils.py +21 -0
- saviialib-1.6.1.dist-info/METADATA +126 -0
- saviialib-1.6.1.dist-info/RECORD +91 -0
- saviialib-1.6.1.dist-info/WHEEL +4 -0
- 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
|
+
[](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,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
|
+
|