saviialib 1.0.0__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of saviialib might be problematic. Click here for more details.

Files changed (54) hide show
  1. {saviialib-1.0.0 → saviialib-1.1.0}/PKG-INFO +1 -1
  2. {saviialib-1.0.0 → saviialib-1.1.0}/pyproject.toml +1 -1
  3. saviialib-1.1.0/src/saviialib/libs/ftp_client/clients/ftplib_client.py +58 -0
  4. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/ftp_client/ftp_client.py +4 -1
  5. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/sharepoint_client/__init__.py +2 -0
  6. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py +21 -1
  7. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/sharepoint_client/sharepoint_client.py +4 -0
  8. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/sharepoint_client/sharepoint_client_contract.py +5 -0
  9. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/sharepoint_client/types/sharepoint_client_types.py +5 -0
  10. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/zero_dependency/utils/datetime_utils.py +1 -1
  11. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/use_cases/update_thies_data.py +17 -2
  12. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/use_cases/upload_backup_to_sharepoint.py +37 -13
  13. {saviialib-1.0.0 → saviialib-1.1.0}/LICENSE +0 -0
  14. {saviialib-1.0.0 → saviialib-1.1.0}/README.md +0 -0
  15. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/__init__.py +0 -0
  16. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/general_types/__init__.py +0 -0
  17. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/general_types/api/__init__.py +0 -0
  18. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/general_types/api/epii_api_types.py +0 -0
  19. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/general_types/error_types/__init__.py +0 -0
  20. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/general_types/error_types/api/__init__.py +0 -0
  21. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/general_types/error_types/api/epii_api_error_types.py +0 -0
  22. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/general_types/error_types/common/__init__.py +0 -0
  23. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/general_types/error_types/common/common_types.py +0 -0
  24. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/directory_client/__init__.py +0 -0
  25. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/directory_client/client/os_client.py +0 -0
  26. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/directory_client/directory_client.py +0 -0
  27. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/directory_client/directory_client_contract.py +0 -0
  28. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/directory_client/types/directory_client_types.py +0 -0
  29. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/files_client/__init__.py +0 -0
  30. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/files_client/clients/aiofiles_client.py +0 -0
  31. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/files_client/files_client.py +0 -0
  32. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/files_client/files_client_contract.py +0 -0
  33. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/files_client/types/files_client_types.py +0 -0
  34. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/ftp_client/__init__.py +0 -0
  35. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/ftp_client/clients/__init__.py +0 -0
  36. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/ftp_client/clients/aioftp_client.py +0 -0
  37. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/ftp_client/ftp_client_contract.py +0 -0
  38. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/ftp_client/types/__init__.py +0 -0
  39. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/libs/ftp_client/types/ftp_client_types.py +0 -0
  40. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/__init__.py +0 -0
  41. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/api.py +0 -0
  42. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/controllers/__init__.py +0 -0
  43. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/controllers/types/__init__.py +0 -0
  44. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/controllers/types/update_thies_data_types.py +0 -0
  45. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/controllers/types/upload_backup_to_sharepoint_types.py +0 -0
  46. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/controllers/update_thies_data.py +0 -0
  47. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/controllers/upload_backup_to_sharepoint.py +0 -0
  48. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/use_cases/constants/upload_backup_to_sharepoint_constants.py +0 -0
  49. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/use_cases/types/__init__.py +0 -0
  50. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/use_cases/types/update_thies_data_types.py +0 -0
  51. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/use_cases/types/upload_backup_to_sharepoint_types.py +0 -0
  52. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/utils/__init__.py +0 -0
  53. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/utils/update_thies_data_utils.py +0 -0
  54. {saviialib-1.0.0 → saviialib-1.1.0}/src/saviialib/services/epii/utils/upload_backup_to_sharepoint_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: saviialib
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: A client library for IoT projects in the RCER initiative
5
5
  License: MIT
6
6
  Author: pedropablozavalat
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "saviialib"
3
- version = "1.0.0"
3
+ version = "1.1.0"
4
4
  description = "A client library for IoT projects in the RCER initiative"
5
5
  authors = ["pedropablozavalat"]
6
6
  license = "MIT"
@@ -0,0 +1,58 @@
1
+ import ftplib
2
+ import asyncio
3
+ from io import BytesIO
4
+ from saviialib.libs.ftp_client.ftp_client_contract import (
5
+ FTPClientContract,
6
+ )
7
+ from saviialib.libs.ftp_client.types.ftp_client_types import (
8
+ FtpClientInitArgs,
9
+ FtpListFilesArgs,
10
+ FtpReadFileArgs,
11
+ )
12
+
13
+
14
+ class FtplibClient(FTPClientContract):
15
+ def __init__(self, args: FtpClientInitArgs) -> None:
16
+ self.host = args.config.ftp_host
17
+ self.port = args.config.ftp_port
18
+ self.password = args.config.ftp_password
19
+ self.user = args.config.ftp_user
20
+ self.client = ftplib.FTP(host=self.host, user=self.user, passwd=self.password)
21
+
22
+ async def _async_start(self) -> None:
23
+ try:
24
+ await asyncio.to_thread(self.client.login, self.user, self.password)
25
+ except OSError:
26
+ raise ConnectionRefusedError(
27
+ f"{self.host}:{self.port} isn't active. "
28
+ "Please ensure the server is running and accessible."
29
+ )
30
+ except Exception as error:
31
+ raise ConnectionError(
32
+ f"General connection for {self.host}:{self.port}.", error.__str__()
33
+ )
34
+
35
+ async def list_files(self, args: FtpListFilesArgs) -> list[str]:
36
+ try:
37
+ EXCLUDED_NAMES = [".", ".."]
38
+ await self._async_start()
39
+ await asyncio.to_thread(self.client.cwd, args.path)
40
+ filenames = await asyncio.to_thread(self.client.nlst, args.path)
41
+ return [
42
+ filename for filename in filenames if filename not in EXCLUDED_NAMES
43
+ ]
44
+ except Exception as error:
45
+ raise ConnectionAbortedError(error)
46
+
47
+ async def read_file(self, args: FtpReadFileArgs) -> bytes:
48
+ await self._async_start()
49
+ try:
50
+ file_content = BytesIO()
51
+ await asyncio.to_thread(
52
+ self.client.retrbinary, "RETR " + args.file_path, file_content.write
53
+ )
54
+ await asyncio.to_thread(file_content.seek, 0)
55
+ file_bytes = await asyncio.to_thread(file_content.read)
56
+ return file_bytes
57
+ except Exception as error:
58
+ raise FileNotFoundError(f"File not found: {args.file_path}") from error
@@ -1,10 +1,11 @@
1
1
  from .clients.aioftp_client import AioFTPClient
2
+ from .clients.ftplib_client import FtplibClient
2
3
  from .ftp_client_contract import FTPClientContract
3
4
  from .types.ftp_client_types import FtpClientInitArgs, FtpListFilesArgs, FtpReadFileArgs
4
5
 
5
6
 
6
7
  class FTPClient(FTPClientContract):
7
- CLIENTS = {"aioftp_client"}
8
+ CLIENTS = {"aioftp_client", "ftplib_client"}
8
9
 
9
10
  def __init__(self, args: FtpClientInitArgs) -> None:
10
11
  if args.client_name not in FTPClient.CLIENTS:
@@ -13,6 +14,8 @@ class FTPClient(FTPClientContract):
13
14
 
14
15
  if args.client_name == "aioftp_client":
15
16
  self.client_obj = AioFTPClient(args)
17
+ elif args.client_name == "ftplib_client":
18
+ self.client_obj = FtplibClient(args)
16
19
  self.client_name = args.client_name
17
20
 
18
21
  async def list_files(self, args: FtpListFilesArgs) -> list[str]:
@@ -4,6 +4,7 @@ from .types.sharepoint_client_types import (
4
4
  SpListFilesArgs,
5
5
  SpListFoldersArgs,
6
6
  SpUploadFileArgs,
7
+ SpCreateFolderArgs,
7
8
  )
8
9
 
9
10
  __all__ = [
@@ -12,4 +13,5 @@ __all__ = [
12
13
  "SpListFilesArgs",
13
14
  "SpListFoldersArgs",
14
15
  "SpUploadFileArgs",
16
+ "SpCreateFolderArgs",
15
17
  ]
@@ -1,5 +1,5 @@
1
1
  from typing import Any
2
-
2
+ import json
3
3
  from aiohttp import ClientError, ClientSession
4
4
  from dotenv import load_dotenv
5
5
 
@@ -10,6 +10,7 @@ from saviialib.libs.sharepoint_client.types.sharepoint_client_types import (
10
10
  SpListFilesArgs,
11
11
  SpListFoldersArgs,
12
12
  SpUploadFileArgs,
13
+ SpCreateFolderArgs,
13
14
  SharepointClientInitArgs,
14
15
  )
15
16
 
@@ -134,3 +135,22 @@ class SharepointRestAPI(SharepointClientContract):
134
135
  return await response.json()
135
136
  except ClientError as error:
136
137
  raise ConnectionError(error) from error
138
+
139
+ async def create_folder(self, args: SpCreateFolderArgs):
140
+ try:
141
+ # Load form digest value
142
+ form_digest_value = await self._load_form_digest_value()
143
+ headers = {
144
+ **self.base_headers,
145
+ "X-RequestDigest": form_digest_value,
146
+ }
147
+ body = {"ServerRelativeUrl": f"{args.folder_relative_url}"}
148
+ endpoint = "web/folders"
149
+ response = await self.session.post(
150
+ endpoint.lstrip("/"), data=json.dumps(body), headers=headers
151
+ )
152
+ response.raise_for_status()
153
+ response_json = await response.json()
154
+ return response_json
155
+ except ClientError as error:
156
+ raise ConnectionError(error) from error
@@ -5,6 +5,7 @@ from .types.sharepoint_client_types import (
5
5
  SpListFilesArgs,
6
6
  SpListFoldersArgs,
7
7
  SpUploadFileArgs,
8
+ SpCreateFolderArgs,
8
9
  )
9
10
 
10
11
 
@@ -52,3 +53,6 @@ class SharepointClient(SharepointClientContract):
52
53
 
53
54
  async def upload_file(self, args: SpUploadFileArgs) -> dict:
54
55
  return await self.client_obj.upload_file(args)
56
+
57
+ async def create_folder(self, args: SpCreateFolderArgs) -> dict:
58
+ return await self.client_obj.create_folder(args)
@@ -4,6 +4,7 @@ from .types.sharepoint_client_types import (
4
4
  SpListFilesArgs,
5
5
  SpListFoldersArgs,
6
6
  SpUploadFileArgs,
7
+ SpCreateFolderArgs,
7
8
  )
8
9
 
9
10
 
@@ -19,3 +20,7 @@ class SharepointClientContract(ABC):
19
20
  @abstractmethod
20
21
  async def upload_file(self, args: SpUploadFileArgs) -> dict:
21
22
  pass
23
+
24
+ @abstractmethod
25
+ async def create_folder(self, args: SpCreateFolderArgs) -> dict:
26
+ pass
@@ -23,3 +23,8 @@ class SpUploadFileArgs:
23
23
  folder_relative_url: str
24
24
  file_name: str
25
25
  file_content: bytes = bytes()
26
+
27
+
28
+ @dataclass
29
+ class SpCreateFolderArgs:
30
+ folder_relative_url: str
@@ -2,7 +2,7 @@ from datetime import datetime
2
2
  from zoneinfo import ZoneInfo
3
3
 
4
4
 
5
- def today(timezone: str = "America/Santiago") -> str:
5
+ def today(timezone: str = "America/Santiago") -> datetime:
6
6
  """
7
7
  Return the current date.
8
8
 
@@ -31,6 +31,7 @@ from saviialib.services.epii.use_cases.types import (
31
31
  from saviialib.services.epii.utils import (
32
32
  parse_execute_response,
33
33
  )
34
+ from saviialib.libs.zero_dependency.utils.datetime_utils import today, datetime_to_str
34
35
 
35
36
 
36
37
  class UpdateThiesDataUseCase:
@@ -59,7 +60,7 @@ class UpdateThiesDataUseCase:
59
60
  def _initialize_thies_ftp_client(self, config: FtpClientConfig) -> FTPClient:
60
61
  """Initialize the FTP client."""
61
62
  try:
62
- return FTPClient(FtpClientInitArgs(config, client_name="aioftp_client"))
63
+ return FTPClient(FtpClientInitArgs(config, client_name="ftplib_client"))
63
64
  except RuntimeError as error:
64
65
  raise FtpClientError(error)
65
66
 
@@ -204,6 +205,20 @@ class UpdateThiesDataUseCase:
204
205
 
205
206
  return upload_results
206
207
 
208
+ async def _sync_pending_files(self, thies_files: set, cloud_files: set) -> set:
209
+ uploading = thies_files - cloud_files
210
+
211
+ # Update content of the daily files
212
+ daily_files = {
213
+ prefix + datetime_to_str(today(), date_format="%Y%m%d") + ".BIN"
214
+ for prefix in ["EXT_", "AVG_"]
215
+ }
216
+ for file in daily_files:
217
+ if file in thies_files:
218
+ uploading.add(file)
219
+
220
+ return uploading
221
+
207
222
  async def execute(self):
208
223
  """Synchronize data from the THIES Center to the cloud."""
209
224
  self.logger.debug("[thies_synchronization_lib] Starting ...")
@@ -223,7 +238,7 @@ class UpdateThiesDataUseCase:
223
238
  "[thies_synchronization_lib] Total files fetched from Sharepoint: %s",
224
239
  str(len(cloud_files)),
225
240
  )
226
- self.uploading = thies_files - cloud_files
241
+ self.uploading = await self._sync_pending_files(thies_files, cloud_files)
227
242
  if not self.uploading:
228
243
  raise EmptyDataError(reason="No files to upload.")
229
244
  # Fetch the content of the files to be uploaded from THIES FTP Server
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  from time import time
3
+ from saviialib.libs.zero_dependency.utils.datetime_utils import today, datetime_to_str
3
4
  from logging import Logger
4
5
  from saviialib.general_types.error_types.api.epii_api_error_types import (
5
6
  BackupEmptyError,
@@ -20,7 +21,7 @@ from saviialib.libs.sharepoint_client import (
20
21
  SharepointClient,
21
22
  SharepointClientInitArgs,
22
23
  SpUploadFileArgs,
23
- SpListFoldersArgs,
24
+ SpCreateFolderArgs,
24
25
  )
25
26
  from saviialib.services.epii.utils.upload_backup_to_sharepoint_utils import (
26
27
  calculate_percentage_uploaded,
@@ -40,6 +41,7 @@ class UploadBackupToSharepointUsecase:
40
41
  self.sharepoint_config = input.sharepoint_config
41
42
  self.local_backup_source_path = input.local_backup_source_path
42
43
  self.sharepoint_destination_path = input.sharepoint_destination_path
44
+
43
45
  self.files_client = self._initialize_files_client()
44
46
  self.dir_client = self._initialize_directory_client()
45
47
  self.log_history = []
@@ -61,7 +63,28 @@ class UploadBackupToSharepointUsecase:
61
63
  def _initialize_files_client(self):
62
64
  return FilesClient(FilesClientInitArgs(client_name="aiofiles_client"))
63
65
 
66
+ async def _initialize_backup_base_folder(self):
67
+ local_backup_name = (
68
+ f"/local-backup-{datetime_to_str(today(), date_format='%m-%d-%Y')}"
69
+ )
70
+ local_backup_destination_path = (
71
+ self.sharepoint_destination_path + local_backup_name
72
+ )
73
+ async with self.sharepoint_client:
74
+ await self.sharepoint_client.create_folder(
75
+ SpCreateFolderArgs(folder_relative_url=local_backup_destination_path)
76
+ )
77
+ self.sharepoint_destination_path = local_backup_destination_path
78
+ base_folder_message = (
79
+ "[local_backup_lib] Creating base folder" + local_backup_name
80
+ )
81
+ self.logger.info(base_folder_message)
82
+ self.log_history.append(base_folder_message)
83
+
64
84
  async def _validate_backup_structure(self):
85
+ # Initialize the backup folder
86
+ await self._initialize_backup_base_folder()
87
+
65
88
  # Check if the local path exists in the main directory
66
89
  if not await self.dir_client.path_exists(self.local_backup_source_path):
67
90
  raise BackupSourcePathError(
@@ -74,20 +97,21 @@ class UploadBackupToSharepointUsecase:
74
97
  else []
75
98
  )
76
99
  async with self.sharepoint_client: # type: ignore
77
- response = await self.sharepoint_client.list_folders(
78
- SpListFoldersArgs(folder_relative_url=self.sharepoint_destination_path)
79
- )
80
- sharepoint_directories = [x["Name"] for x in response["value"]] # type: ignore
100
+ for local_dir in local_directories:
101
+ create_message = (
102
+ f"[local_backup_lib] Creating a new directory '{local_dir}'."
103
+ )
81
104
 
82
- # Verify that all local directories exist in SharePoint
83
- for local_dir in local_directories:
84
- if local_dir not in sharepoint_directories:
85
- error_message = f"Folder '{local_dir}' does not exist in SharePoint destination path '{self.sharepoint_destination_path}'."
86
- self.log_history.append(error_message)
87
- self.logger.error("[local_backup_lib] %s", error_message)
88
- raise BackupSourcePathError(
89
- reason=f"Folder '{local_dir}' does not exist in SharePoint destination path '{self.sharepoint_destination_path}'."
105
+ self.log_history.append(create_message)
106
+ await self.sharepoint_client.create_folder(
107
+ SpCreateFolderArgs(
108
+ folder_relative_url=self.sharepoint_destination_path
109
+ + "/"
110
+ + local_dir
111
+ )
90
112
  )
113
+ self.logger.info("[local_backup_lib] %s", create_message)
114
+ self.log_history.append(f"[local_backup_lib] {create_message}")
91
115
 
92
116
  # Check if the current folder only have files and each folder exist in Microsoft Sharepoint.
93
117
  if self.total_files == 0:
File without changes
File without changes