saviialib 0.8.0__tar.gz → 0.9.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of saviialib might be problematic. Click here for more details.
- {saviialib-0.8.0 → saviialib-0.9.1}/PKG-INFO +1 -1
- {saviialib-0.8.0 → saviialib-0.9.1}/pyproject.toml +1 -1
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/api/epii_api_types.py +1 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/api.py +2 -1
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/controllers/upload_backup_to_sharepoint.py +1 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/use_cases/constants/upload_backup_to_sharepoint_constants.py +4 -4
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/use_cases/types/upload_backup_to_sharepoint_types.py +1 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/use_cases/upload_backup_to_sharepoint.py +59 -19
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/utils/upload_backup_to_sharepoint_utils.py +22 -12
- {saviialib-0.8.0 → saviialib-0.9.1}/LICENSE +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/README.md +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/api/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/error_types/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/error_types/api/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/error_types/api/epii_api_error_types.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/error_types/common/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/error_types/common/common_types.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/files_client/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/files_client/clients/aiofiles_client.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/files_client/files_client.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/files_client/files_client_contract.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/files_client/types/files_client_types.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/ftp_client/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/ftp_client/clients/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/ftp_client/clients/aioftp_client.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/ftp_client/ftp_client.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/ftp_client/ftp_client_contract.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/ftp_client/types/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/ftp_client/types/ftp_client_types.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/sharepoint_client/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/sharepoint_client/sharepoint_client.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/sharepoint_client/sharepoint_client_contract.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/sharepoint_client/types/sharepoint_client_types.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/zero_dependency/utils/datetime_utils.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/controllers/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/controllers/types/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/controllers/types/update_thies_data_types.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/controllers/types/upload_backup_to_sharepoint_types.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/controllers/update_thies_data.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/use_cases/constants/update_thies_data_constants.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/use_cases/types/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/use_cases/types/update_thies_data_types.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/use_cases/update_thies_data.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/utils/__init__.py +0 -0
- {saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/utils/update_thies_data_utils.py +0 -0
|
@@ -53,7 +53,7 @@ class EpiiAPI:
|
|
|
53
53
|
return response.__dict__
|
|
54
54
|
|
|
55
55
|
async def upload_backup_to_sharepoint(
|
|
56
|
-
self, local_backup_source_path: str
|
|
56
|
+
self, local_backup_source_path: str, destination_folders: dict[str, str]
|
|
57
57
|
) -> Dict[str, Any]:
|
|
58
58
|
"""Migrate a backup folder from Home assistant to Sharepoint directory.
|
|
59
59
|
Args:
|
|
@@ -70,6 +70,7 @@ class EpiiAPI:
|
|
|
70
70
|
sharepoint_tenant_id=self.sharepoint_tenant_id,
|
|
71
71
|
sharepoint_tenant_name=self.sharepoint_tenant_name,
|
|
72
72
|
local_backup_source_path=local_backup_source_path,
|
|
73
|
+
destination_folders=destination_folders
|
|
73
74
|
)
|
|
74
75
|
|
|
75
76
|
controller = UploadBackupToSharepointController(
|
|
@@ -28,34 +28,40 @@ from saviialib.services.epii.utils.upload_backup_to_sharepoint_utils import (
|
|
|
28
28
|
from .types.upload_backup_to_sharepoint_types import (
|
|
29
29
|
UploadBackupToSharepointUseCaseInput,
|
|
30
30
|
)
|
|
31
|
+
from .constants.upload_backup_to_sharepoint_constants import LOGGER
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class UploadBackupToSharepointUsecase:
|
|
34
35
|
def __init__(self, input: UploadBackupToSharepointUseCaseInput):
|
|
35
36
|
self.sharepoint_config = input.sharepoint_config
|
|
36
37
|
self.local_backup_source_path = input.local_backup_source_path
|
|
37
|
-
self.
|
|
38
|
+
self.destination_folders = input.destination_folders
|
|
38
39
|
self.files_client = self._initialize_files_client()
|
|
39
|
-
self.total_files = sum(
|
|
40
|
-
len(files) for files in self.grouped_files_by_folder.values()
|
|
41
|
-
)
|
|
42
40
|
self.log_history = []
|
|
41
|
+
self.grouped_files_by_folder = None
|
|
42
|
+
self.total_files = None
|
|
43
43
|
|
|
44
44
|
def _initialize_files_client(self):
|
|
45
45
|
return FilesClient(FilesClientInitArgs(client_name="aiofiles_client"))
|
|
46
46
|
|
|
47
|
-
def _extract_filesnames_by_folder(self) -> dict[str, list[str]]:
|
|
47
|
+
async def _extract_filesnames_by_folder(self) -> dict[str, list[str]]:
|
|
48
48
|
"""Groups files by their parent folder."""
|
|
49
|
-
|
|
49
|
+
backup_folder_exists = await asyncio.to_thread(
|
|
50
|
+
os.path.exists, self.local_backup_source_path
|
|
51
|
+
)
|
|
52
|
+
if not backup_folder_exists:
|
|
50
53
|
return {}
|
|
54
|
+
folder_names = await asyncio.to_thread(
|
|
55
|
+
os.listdir, self.local_backup_source_path
|
|
56
|
+
)
|
|
51
57
|
return {
|
|
52
58
|
folder_name: [
|
|
53
59
|
file_name
|
|
54
|
-
for file_name in
|
|
55
|
-
os.path.join(self.local_backup_source_path, folder_name)
|
|
60
|
+
for file_name in await asyncio.to_thread(
|
|
61
|
+
os.listdir, os.path.join(self.local_backup_source_path, folder_name)
|
|
56
62
|
)
|
|
57
63
|
]
|
|
58
|
-
for folder_name in
|
|
64
|
+
for folder_name in folder_names
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
def _save_log_history(self) -> None:
|
|
@@ -82,7 +88,9 @@ class UploadBackupToSharepointUsecase:
|
|
|
82
88
|
|
|
83
89
|
async with sharepoint_client:
|
|
84
90
|
try:
|
|
85
|
-
destination_folder =
|
|
91
|
+
destination_folder = self.destination_folders.get(
|
|
92
|
+
folder_name, folder_name
|
|
93
|
+
)
|
|
86
94
|
folder_url = f"{c.SHAREPOINT_BASE_URL}/{destination_folder}"
|
|
87
95
|
args = SpUploadFileArgs(
|
|
88
96
|
folder_relative_url=folder_url,
|
|
@@ -103,14 +111,14 @@ class UploadBackupToSharepointUsecase:
|
|
|
103
111
|
f"[BACKUP] Uploading file '{file_name}' from '{folder_name}' "
|
|
104
112
|
)
|
|
105
113
|
self.log_history.append(uploading_message)
|
|
106
|
-
|
|
114
|
+
LOGGER.debug(uploading_message)
|
|
107
115
|
file_path = os.path.join(self.local_backup_source_path, folder_name, file_name)
|
|
108
116
|
file_content = await self.files_client.read(ReadArgs(file_path, mode="rb"))
|
|
109
117
|
uploaded, error_message = await self.export_file_to_sharepoint(
|
|
110
118
|
folder_name, file_name, file_content
|
|
111
119
|
)
|
|
112
120
|
result_message = show_upload_result(uploaded, file_name)
|
|
113
|
-
|
|
121
|
+
LOGGER.debug(result_message)
|
|
114
122
|
self.log_history.append(result_message)
|
|
115
123
|
return {
|
|
116
124
|
"parent_folder": folder_name,
|
|
@@ -126,7 +134,7 @@ class UploadBackupToSharepointUsecase:
|
|
|
126
134
|
f"[BACKUP] Retrying upload for {len(failed_files)} failed files... 🚨"
|
|
127
135
|
)
|
|
128
136
|
self.log_history.append(retry_message)
|
|
129
|
-
|
|
137
|
+
LOGGER.debug(retry_message)
|
|
130
138
|
for file in failed_files:
|
|
131
139
|
tasks.append(
|
|
132
140
|
self.upload_and_log_progress_task(
|
|
@@ -142,40 +150,72 @@ class UploadBackupToSharepointUsecase:
|
|
|
142
150
|
"[BACKUP] All files uploaded successfully after retry."
|
|
143
151
|
)
|
|
144
152
|
self.log_history.append(successful_upload_retry)
|
|
145
|
-
|
|
153
|
+
LOGGER.debug(successful_upload_retry)
|
|
146
154
|
self._save_log_history()
|
|
147
155
|
return parse_execute_response(results)
|
|
148
156
|
|
|
149
157
|
async def execute(self):
|
|
150
158
|
"""Exports all files from the local backup folder to SharePoint cloud."""
|
|
159
|
+
self.grouped_files_by_folder = await self._extract_filesnames_by_folder()
|
|
160
|
+
self.total_files = sum(
|
|
161
|
+
len(files) for files in self.grouped_files_by_folder.values()
|
|
162
|
+
)
|
|
151
163
|
tasks = []
|
|
152
164
|
start_time = time()
|
|
153
|
-
|
|
165
|
+
|
|
166
|
+
# Check if the local path exists in the main directory
|
|
167
|
+
if not await directory_exists(self.local_backup_source_path):
|
|
154
168
|
raise BackupSourcePathError(
|
|
155
169
|
reason=f"'{self.local_backup_source_path}' doesn't exist."
|
|
156
170
|
)
|
|
171
|
+
|
|
172
|
+
# Check if the current folder only have files.
|
|
173
|
+
items = [
|
|
174
|
+
item
|
|
175
|
+
for item in await asyncio.to_thread(
|
|
176
|
+
os.listdir, self.local_backup_source_path
|
|
177
|
+
)
|
|
178
|
+
]
|
|
179
|
+
for item in items:
|
|
180
|
+
folder_included = item in self.destination_folders.keys()
|
|
181
|
+
is_file = not await asyncio.to_thread(
|
|
182
|
+
os.path.isdir, os.path.join(self.local_backup_source_path, item)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if not folder_included and not is_file:
|
|
186
|
+
raise BackupSourcePathError(
|
|
187
|
+
reason=(
|
|
188
|
+
f"'{item}' must be included in the destination folders dictionary",
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
elif folder_included and is_file:
|
|
192
|
+
print(folder_included, is_file)
|
|
193
|
+
raise BackupSourcePathError(reason=(f"'{item}' must be a directory.",))
|
|
194
|
+
|
|
157
195
|
if self.total_files == 0:
|
|
158
196
|
no_files_message = (
|
|
159
197
|
f"[BACKUP] {self.local_backup_source_path} has no files ⚠️"
|
|
160
198
|
)
|
|
161
199
|
self.log_history.append(no_files_message)
|
|
162
|
-
|
|
200
|
+
LOGGER.debug(no_files_message)
|
|
163
201
|
raise BackupEmptyError
|
|
164
202
|
# Create task for each file stored in the the local backup folder.
|
|
165
203
|
for folder_name in self.grouped_files_by_folder:
|
|
166
204
|
if (
|
|
167
|
-
count_files_in_directory(
|
|
205
|
+
await count_files_in_directory(
|
|
206
|
+
self.local_backup_source_path, folder_name
|
|
207
|
+
)
|
|
168
208
|
== 0
|
|
169
209
|
):
|
|
170
210
|
empty_folder_message = f"[BACKUP] The folder '{folder_name}' is empty ⚠️"
|
|
171
|
-
|
|
211
|
+
LOGGER.debug(empty_folder_message)
|
|
172
212
|
self.log_history.append(empty_folder_message)
|
|
173
213
|
continue
|
|
174
214
|
extracting_files_message = (
|
|
175
215
|
"[BACKUP]" + f" Extracting files from '{folder_name} ".center(15, "*")
|
|
176
216
|
)
|
|
177
217
|
self.log_history.append(extracting_files_message)
|
|
178
|
-
|
|
218
|
+
LOGGER.debug(extracting_files_message)
|
|
179
219
|
for file_name in self.grouped_files_by_folder[folder_name]:
|
|
180
220
|
tasks.append(self.upload_and_log_progress_task(folder_name, file_name))
|
|
181
221
|
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from typing import List, Dict, Optional
|
|
3
|
+
import asyncio
|
|
3
4
|
import os
|
|
5
|
+
from saviialib.general_types.error_types.api.epii_api_error_types import (
|
|
6
|
+
BackupSourcePathError,
|
|
7
|
+
)
|
|
8
|
+
from saviialib.services.epii.use_cases.constants.upload_backup_to_sharepoint_constants import (
|
|
9
|
+
LOGGER,
|
|
10
|
+
)
|
|
4
11
|
|
|
5
12
|
|
|
6
13
|
def extract_error_information(error: str) -> Optional[Dict[str, str]]:
|
|
@@ -24,7 +31,7 @@ def explain_status_code(status_code: int) -> str:
|
|
|
24
31
|
|
|
25
32
|
|
|
26
33
|
def extract_error_message(results: List[Dict], success: float) -> str:
|
|
27
|
-
|
|
34
|
+
LOGGER.info(
|
|
28
35
|
"[BACKUP] Not all files uploaded ⚠️\n"
|
|
29
36
|
f"[BACKUP] Files failed to upload: {(1 - success):.2%}"
|
|
30
37
|
)
|
|
@@ -52,9 +59,9 @@ def extract_error_message(results: List[Dict], success: float) -> str:
|
|
|
52
59
|
|
|
53
60
|
# Summary
|
|
54
61
|
for code, items in grouped_errors.items():
|
|
55
|
-
|
|
62
|
+
LOGGER.info(f"[BACKUP] Status code {code} - {explain_status_code(int(code))}")
|
|
56
63
|
for item in items:
|
|
57
|
-
|
|
64
|
+
LOGGER.info(
|
|
58
65
|
f"[BACKUP] File {item['file_name']}, url: {item['url']}, message: {item['message']}"
|
|
59
66
|
)
|
|
60
67
|
|
|
@@ -63,11 +70,14 @@ def extract_error_message(results: List[Dict], success: float) -> str:
|
|
|
63
70
|
|
|
64
71
|
|
|
65
72
|
def parse_execute_response(results: List[Dict]) -> Dict[str, List[str]]:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
try:
|
|
74
|
+
return {
|
|
75
|
+
"new_files": len(
|
|
76
|
+
[item["file_name"] for item in results if item.get("uploaded")]
|
|
77
|
+
),
|
|
78
|
+
}
|
|
79
|
+
except (IsADirectoryError, AttributeError, ConnectionError) as error:
|
|
80
|
+
raise BackupSourcePathError(reason=error)
|
|
71
81
|
|
|
72
82
|
|
|
73
83
|
def show_upload_result(uploaded: bool, file_name: str) -> str:
|
|
@@ -84,9 +94,9 @@ def calculate_percentage_uploaded(results: List[Dict], total_files: int) -> floa
|
|
|
84
94
|
return (uploaded_count / total_files) * 100 if total_files > 0 else 0
|
|
85
95
|
|
|
86
96
|
|
|
87
|
-
def directory_exists(path: str) -> bool:
|
|
88
|
-
return os.path.exists
|
|
97
|
+
async def directory_exists(path: str) -> bool:
|
|
98
|
+
return await asyncio.to_thread(os.path.exists, path)
|
|
89
99
|
|
|
90
100
|
|
|
91
|
-
def count_files_in_directory(path: str, folder_name: str) -> int:
|
|
92
|
-
return len(os.listdir
|
|
101
|
+
async def count_files_in_directory(path: str, folder_name: str) -> int:
|
|
102
|
+
return len(await asyncio.to_thread(os.listdir, os.path.join(path, folder_name)))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/error_types/common/__init__.py
RENAMED
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/general_types/error_types/common/common_types.py
RENAMED
|
File without changes
|
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/files_client/clients/aiofiles_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/files_client/files_client_contract.py
RENAMED
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/files_client/types/files_client_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/sharepoint_client/sharepoint_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/libs/zero_dependency/utils/datetime_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/controllers/types/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/controllers/update_thies_data.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/use_cases/update_thies_data.py
RENAMED
|
File without changes
|
|
File without changes
|
{saviialib-0.8.0 → saviialib-0.9.1}/src/saviialib/services/epii/utils/update_thies_data_utils.py
RENAMED
|
File without changes
|