saviialib 0.10.2__tar.gz → 0.11.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 (55) hide show
  1. {saviialib-0.10.2 → saviialib-0.11.0}/PKG-INFO +1 -1
  2. {saviialib-0.10.2 → saviialib-0.11.0}/pyproject.toml +1 -1
  3. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/general_types/api/epii_api_types.py +0 -2
  4. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/general_types/error_types/api/epii_api_error_types.py +12 -0
  5. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/ftp_client/clients/aioftp_client.py +9 -5
  6. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/ftp_client/ftp_client.py +4 -4
  7. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/ftp_client/ftp_client_contract.py +2 -2
  8. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py +3 -2
  9. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/sharepoint_client/sharepoint_client.py +21 -1
  10. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/api.py +21 -11
  11. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/controllers/types/update_thies_data_types.py +2 -0
  12. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/controllers/types/upload_backup_to_sharepoint_types.py +2 -0
  13. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/controllers/update_thies_data.py +12 -2
  14. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/controllers/upload_backup_to_sharepoint.py +3 -3
  15. saviialib-0.11.0/src/saviialib/services/epii/use_cases/constants/upload_backup_to_sharepoint_constants.py +5 -0
  16. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/use_cases/types/update_thies_data_types.py +3 -1
  17. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/use_cases/types/upload_backup_to_sharepoint_types.py +1 -1
  18. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/use_cases/update_thies_data.py +63 -28
  19. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/use_cases/upload_backup_to_sharepoint.py +98 -81
  20. saviialib-0.10.2/src/saviialib/services/epii/use_cases/constants/update_thies_data_constants.py +0 -5
  21. saviialib-0.10.2/src/saviialib/services/epii/use_cases/constants/upload_backup_to_sharepoint_constants.py +0 -6
  22. {saviialib-0.10.2 → saviialib-0.11.0}/LICENSE +0 -0
  23. {saviialib-0.10.2 → saviialib-0.11.0}/README.md +0 -0
  24. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/__init__.py +0 -0
  25. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/general_types/__init__.py +0 -0
  26. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/general_types/api/__init__.py +0 -0
  27. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/general_types/error_types/__init__.py +0 -0
  28. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/general_types/error_types/api/__init__.py +0 -0
  29. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/general_types/error_types/common/__init__.py +0 -0
  30. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/general_types/error_types/common/common_types.py +0 -0
  31. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/directory_client/__init__.py +0 -0
  32. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/directory_client/client/os_client.py +0 -0
  33. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/directory_client/directory_client.py +0 -0
  34. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/directory_client/directory_client_contract.py +0 -0
  35. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/directory_client/types/directory_client_types.py +0 -0
  36. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/files_client/__init__.py +0 -0
  37. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/files_client/clients/aiofiles_client.py +0 -0
  38. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/files_client/files_client.py +0 -0
  39. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/files_client/files_client_contract.py +0 -0
  40. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/files_client/types/files_client_types.py +0 -0
  41. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/ftp_client/__init__.py +0 -0
  42. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/ftp_client/clients/__init__.py +0 -0
  43. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/ftp_client/types/__init__.py +0 -0
  44. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/ftp_client/types/ftp_client_types.py +0 -0
  45. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/sharepoint_client/__init__.py +0 -0
  46. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/sharepoint_client/sharepoint_client_contract.py +0 -0
  47. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/sharepoint_client/types/sharepoint_client_types.py +0 -0
  48. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/libs/zero_dependency/utils/datetime_utils.py +0 -0
  49. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/__init__.py +0 -0
  50. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/controllers/__init__.py +0 -0
  51. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/controllers/types/__init__.py +0 -0
  52. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/use_cases/types/__init__.py +0 -0
  53. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/utils/__init__.py +0 -0
  54. {saviialib-0.10.2 → saviialib-0.11.0}/src/saviialib/services/epii/utils/update_thies_data_utils.py +0 -0
  55. {saviialib-0.10.2 → saviialib-0.11.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: 0.10.2
3
+ Version: 0.11.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 = "0.10.2"
3
+ version = "0.11.0"
4
4
  description = "A client library for IoT projects in the RCER initiative"
5
5
  authors = ["pedropablozavalat"]
6
6
  license = "MIT"
@@ -80,6 +80,4 @@ class EpiiSharepointBackupConfig:
80
80
  sharepoint_tenant_id: str
81
81
  sharepoint_tenant_name: str
82
82
  sharepoint_site_name: str
83
- local_backup_source_path: str
84
- destination_folders: str
85
83
  logger: Logger
@@ -43,6 +43,18 @@ class SharePointFetchingError(Exception):
43
43
  return self.reason.__str__()
44
44
 
45
45
 
46
+ class SharePointDirectoryError(Exception):
47
+ def __init__(self, *args, reason):
48
+ super().__init__(*args, reason)
49
+ self.reason = reason
50
+
51
+ def __str__(self):
52
+ return (
53
+ "An error occurred while fetching the folders from Microsoft SharePoint. "
54
+ + self.reason.__str__()
55
+ )
56
+
57
+
46
58
  class SharePointUploadError(Exception):
47
59
  """Raised when there is an error uploading files to the Microsoft SharePoint folder."""
48
60
 
@@ -34,12 +34,16 @@ class AioFTPClient(FTPClientContract):
34
34
  )
35
35
 
36
36
  async def list_files(self, args: FtpListFilesArgs) -> list[str]:
37
- await self._async_start()
38
- return [
39
- path.name async for path, _ in self.client.list(args.path, recursive=False)
40
- ]
37
+ try:
38
+ await self._async_start()
39
+ return [
40
+ path.name
41
+ async for path, _ in self.client.list(args.path, recursive=False) # type: ignore
42
+ ]
43
+ except StatusCodeError as error:
44
+ raise ConnectionAbortedError(error)
41
45
 
42
46
  async def read_file(self, args: FtpReadFileArgs) -> bytes:
43
47
  await self._async_start()
44
- async with self.client.download_stream(args.file_path) as stream:
48
+ async with self.client.download_stream(args.file_path) as stream: # type: ignore
45
49
  return await stream.read()
@@ -15,8 +15,8 @@ class FTPClient(FTPClientContract):
15
15
  self.client_obj = AioFTPClient(args)
16
16
  self.client_name = args.client_name
17
17
 
18
- def list_files(self, args: FtpListFilesArgs) -> list[str]:
19
- return self.client_obj.list_files(args)
18
+ async def list_files(self, args: FtpListFilesArgs) -> list[str]:
19
+ return await self.client_obj.list_files(args)
20
20
 
21
- def read_file(self, args: FtpReadFileArgs) -> bytes:
22
- return self.client_obj.read_file(args)
21
+ async def read_file(self, args: FtpReadFileArgs) -> bytes:
22
+ return await self.client_obj.read_file(args)
@@ -5,9 +5,9 @@ from .types.ftp_client_types import FtpListFilesArgs, FtpReadFileArgs
5
5
 
6
6
  class FTPClientContract(ABC):
7
7
  @abstractmethod
8
- def list_files(self, args: FtpListFilesArgs) -> list[str]:
8
+ async def list_files(self, args: FtpListFilesArgs) -> list[str]:
9
9
  pass
10
10
 
11
11
  @abstractmethod
12
- def read_file(self, args: FtpReadFileArgs) -> bytes:
12
+ async def read_file(self, args: FtpReadFileArgs) -> bytes:
13
13
  pass
@@ -104,10 +104,11 @@ class SharepointRestAPI(SharepointClientContract):
104
104
  folder_relative_url = (
105
105
  f"GetFolderByServerRelativeUrl('{args.folder_relative_url}')"
106
106
  )
107
- endpoint = f"web/{folder_relative_url}/Folder"
107
+ endpoint = f"web/{folder_relative_url}/Folders"
108
108
  response = await self.session.get(endpoint.lstrip("/"))
109
109
  response.raise_for_status()
110
- return await response.json()
110
+ response_json = await response.json()
111
+ return response_json
111
112
  except ClientError as error:
112
113
  raise ConnectionError(error) from error
113
114
 
@@ -18,6 +18,26 @@ class SharepointClient(SharepointClientContract):
18
18
  elif args.client_name == "sharepoint_rest_api":
19
19
  self.client_obj = SharepointRestAPI(args)
20
20
 
21
+ @property
22
+ def tenant_id(self):
23
+ return self.client_obj.tenant_id
24
+
25
+ @property
26
+ def tenant_name(self):
27
+ return self.client_obj.tenant_name
28
+
29
+ @property
30
+ def site_name(self):
31
+ return self.client_obj.site_name
32
+
33
+ @property
34
+ def client_id(self):
35
+ return self.client_obj.client_id
36
+
37
+ @property
38
+ def client_secret(self):
39
+ return self.client_obj.client_secret
40
+
21
41
  async def __aenter__(self):
22
42
  return await self.client_obj.__aenter__()
23
43
 
@@ -28,7 +48,7 @@ class SharepointClient(SharepointClientContract):
28
48
  return await self.client_obj.list_files(args)
29
49
 
30
50
  async def list_folders(self, args: SpListFoldersArgs) -> list:
31
- return await self.client_obj.list_files(args)
51
+ return await self.client_obj.list_folders(args)
32
52
 
33
53
  async def upload_file(self, args: SpUploadFileArgs) -> dict:
34
54
  return await self.client_obj.upload_file(args)
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict
1
+ from typing import Any, Dict, List
2
2
 
3
3
  from .controllers.types.update_thies_data_types import UpdateThiesDataControllerInput
4
4
  from .controllers.types.upload_backup_to_sharepoint_types import (
@@ -28,15 +28,21 @@ class EpiiAPI:
28
28
  self.sharepoint_tenant_id = config.sharepoint_tenant_id
29
29
  self.sharepoint_tenant_name = config.sharepoint_tenant_name
30
30
  self.sharepoint_site_name = config.sharepoint_site_name
31
+
31
32
  self.logger = config.logger
32
33
 
33
- async def update_thies_data(self) -> Dict[str, Any]:
34
- """
35
- This method establishes a connection to an FTP server using the provided
36
- credentials and updates data related to THIES Data Logger.
34
+ async def update_thies_data(
35
+ self, sharepoint_folders_path: List[str], ftp_server_folders_path: List[str]
36
+ ) -> Dict[str, Any]:
37
+ """Updates data from a THIES Data Logger by connecting to an FTP server
38
+ and transferring data to specified Sharepoint folders.
39
+
40
+ Args:
41
+ sharepoint_folders_path (list): List of Sharepoint folder paths for AVG and EXT data.
42
+ ftp_server_folders_path (list): List of FTP server folder paths for AVG and EXT data.
37
43
 
38
44
  Returns:
39
- response (dict): A dictionary representation of the API response.
45
+ dict: A dictionary representation of the API response.
40
46
  """
41
47
  config = EpiiUpdateThiesConfig(
42
48
  ftp_port=self.ftp_port,
@@ -49,12 +55,16 @@ class EpiiAPI:
49
55
  sharepoint_tenant_id=self.sharepoint_tenant_id,
50
56
  sharepoint_tenant_name=self.sharepoint_tenant_name,
51
57
  )
52
- controller = UpdateThiesDataController(UpdateThiesDataControllerInput(config))
58
+ controller = UpdateThiesDataController(
59
+ UpdateThiesDataControllerInput(
60
+ config, sharepoint_folders_path, ftp_server_folders_path
61
+ )
62
+ )
53
63
  response = await controller.execute()
54
64
  return response.__dict__
55
65
 
56
66
  async def upload_backup_to_sharepoint(
57
- self, local_backup_source_path: str, destination_folders: dict[str, str]
67
+ self, local_backup_source_path: str, sharepoint_destination_path: str
58
68
  ) -> Dict[str, Any]:
59
69
  """Migrate a backup folder from Home assistant to Sharepoint directory.
60
70
  Args:
@@ -70,13 +80,13 @@ class EpiiAPI:
70
80
  sharepoint_site_name=self.sharepoint_site_name,
71
81
  sharepoint_tenant_id=self.sharepoint_tenant_id,
72
82
  sharepoint_tenant_name=self.sharepoint_tenant_name,
73
- local_backup_source_path=local_backup_source_path,
74
- destination_folders=destination_folders,
75
83
  logger=self.logger,
76
84
  )
77
85
 
78
86
  controller = UploadBackupToSharepointController(
79
- UploadBackupToSharepointControllerInput(config)
87
+ UploadBackupToSharepointControllerInput(
88
+ config, local_backup_source_path, sharepoint_destination_path
89
+ )
80
90
  )
81
91
  response = await controller.execute()
82
92
  return response.__dict__
@@ -8,6 +8,8 @@ from saviialib.general_types.api.epii_api_types import (
8
8
  @dataclass
9
9
  class UpdateThiesDataControllerInput:
10
10
  config: EpiiUpdateThiesConfig
11
+ sharepoint_folders_path: list
12
+ ftp_server_folders_path: list
11
13
 
12
14
 
13
15
  @dataclass
@@ -7,6 +7,8 @@ from saviialib.general_types.api.epii_api_types import EpiiSharepointBackupConfi
7
7
  @dataclass
8
8
  class UploadBackupToSharepointControllerInput:
9
9
  config: EpiiSharepointBackupConfig
10
+ local_backup_source_path: str
11
+ sharepoint_destination_path: str
10
12
 
11
13
 
12
14
  @dataclass
@@ -5,6 +5,7 @@ from saviialib.general_types.error_types.api.epii_api_error_types import (
5
5
  ThiesConnectionError,
6
6
  ThiesFetchingError,
7
7
  SharePointUploadError,
8
+ SharePointDirectoryError,
8
9
  )
9
10
  from saviialib.general_types.error_types.common.common_types import (
10
11
  EmptyDataError,
@@ -42,6 +43,8 @@ class UpdateThiesDataController:
42
43
  sharepoint_tenant_name=input.config.sharepoint_tenant_name,
43
44
  sharepoint_tenant_id=input.config.sharepoint_tenant_id,
44
45
  ),
46
+ sharepoint_folders_path=input.sharepoint_folders_path,
47
+ ftp_server_folders_path=input.ftp_server_folders_path,
45
48
  )
46
49
  )
47
50
 
@@ -51,7 +54,7 @@ class UpdateThiesDataController:
51
54
  return UpdateThiesDataControllerOutput(
52
55
  message="THIES was synced successfully",
53
56
  status=HTTPStatus.OK.value,
54
- metadata={"data": data},
57
+ metadata={"data": data}, # type: ignore
55
58
  )
56
59
  except EmptyDataError:
57
60
  return UpdateThiesDataControllerOutput(
@@ -87,7 +90,14 @@ class UpdateThiesDataController:
87
90
 
88
91
  except SharePointUploadError as error:
89
92
  return UpdateThiesDataControllerOutput(
90
- message="An error oucrred while uploading files to RCER Cloud",
93
+ message="An error ocurred while uploading files to RCER Cloud",
94
+ status=HTTPStatus.BAD_REQUEST.value,
95
+ metadata={"error": error.__str__()},
96
+ )
97
+
98
+ except SharePointDirectoryError as error:
99
+ return UpdateThiesDataControllerOutput(
100
+ message="An error ocurred while extracting folders from Microsoft Sharepoint",
91
101
  status=HTTPStatus.BAD_REQUEST.value,
92
102
  metadata={"error": error.__str__()},
93
103
  )
@@ -33,8 +33,8 @@ class UploadBackupToSharepointController:
33
33
  sharepoint_tenant_name=input.config.sharepoint_tenant_name,
34
34
  sharepoint_tenant_id=input.config.sharepoint_tenant_id,
35
35
  ),
36
- local_backup_source_path=input.config.local_backup_source_path,
37
- destination_folders=input.config.destination_folders,
36
+ local_backup_source_path=input.local_backup_source_path,
37
+ sharepoint_destination_path=input.sharepoint_destination_path,
38
38
  logger=input.config.logger,
39
39
  )
40
40
  )
@@ -45,7 +45,7 @@ class UploadBackupToSharepointController:
45
45
  return UploadBackupToSharepointControllerOutput(
46
46
  message="Local backup was migrated successfully",
47
47
  status=HTTPStatus.OK.value,
48
- metadata={"data": data},
48
+ metadata={"data": data}, # type: ignore
49
49
  )
50
50
  except EmptyDataError:
51
51
  return UploadBackupToSharepointControllerOutput(
@@ -0,0 +1,5 @@
1
+ import logging
2
+
3
+
4
+ logging.basicConfig(level=logging.DEBUG)
5
+ LOGGER = logging.getLogger(__package__)
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Dict
2
+ from typing import Dict, List
3
3
  from saviialib.general_types.api.epii_api_types import FtpClientConfig, SharepointConfig
4
4
 
5
5
 
@@ -7,6 +7,8 @@ from saviialib.general_types.api.epii_api_types import FtpClientConfig, Sharepoi
7
7
  class UpdateThiesDataUseCaseInput:
8
8
  ftp_config: FtpClientConfig
9
9
  sharepoint_config: SharepointConfig
10
+ sharepoint_folders_path: List
11
+ ftp_server_folders_path: List
10
12
 
11
13
 
12
14
  @dataclass
@@ -7,5 +7,5 @@ from logging import Logger
7
7
  class UploadBackupToSharepointUseCaseInput:
8
8
  sharepoint_config: SharepointConfig
9
9
  local_backup_source_path: str
10
- destination_folders: dict
10
+ sharepoint_destination_path: str
11
11
  logger: Logger
@@ -1,6 +1,6 @@
1
- import saviialib.services.epii.use_cases.constants.update_thies_data_constants as c
2
1
  from saviialib.general_types.error_types.api.epii_api_error_types import (
3
2
  SharePointFetchingError,
3
+ SharePointDirectoryError,
4
4
  SharePointUploadError,
5
5
  ThiesConnectionError,
6
6
  ThiesFetchingError,
@@ -20,6 +20,7 @@ from saviialib.libs.sharepoint_client import (
20
20
  SharepointClient,
21
21
  SharepointClientInitArgs,
22
22
  SpListFilesArgs,
23
+ SpListFoldersArgs,
23
24
  SpUploadFileArgs,
24
25
  )
25
26
  from saviialib.services.epii.use_cases.types import (
@@ -38,6 +39,9 @@ class UpdateThiesDataUseCase:
38
39
  input.sharepoint_config
39
40
  )
40
41
  self.thies_ftp_client = self._initialize_thies_ftp_client(input.ftp_config)
42
+ self.sharepoint_folders_path = input.sharepoint_folders_path
43
+ self.ftp_server_folders_path = input.ftp_server_folders_path
44
+ self.sharepoint_base_url = f"/sites/{self.sharepoint_client.site_name}"
41
45
  self.uploading = set()
42
46
 
43
47
  def _initialize_sharepoint_client(
@@ -58,36 +62,56 @@ class UpdateThiesDataUseCase:
58
62
  except RuntimeError as error:
59
63
  raise FtpClientError(error)
60
64
 
65
+ async def _validate_sharepoint_current_folders(self):
66
+ async with self.sharepoint_client:
67
+ folder_base_path = "/".join(
68
+ self.sharepoint_folders_path[0].split("/")[0:-1]
69
+ )
70
+ relative_url = f"{self.sharepoint_base_url}/{folder_base_path}"
71
+ response = await self.sharepoint_client.list_folders(
72
+ SpListFoldersArgs(relative_url)
73
+ )
74
+
75
+ current_folders = [item["Name"] for item in response["value"]] # type: ignore
76
+
77
+ for folder_path in self.sharepoint_folders_path:
78
+ folder_name = folder_path.split("/")[-1]
79
+ if folder_name not in current_folders:
80
+ raise SharePointDirectoryError(
81
+ reason=f"The current folder '{folder_name}' doesn't exist."
82
+ )
83
+
61
84
  async def fetch_cloud_file_names(self) -> set[str]:
62
85
  """Fetch file names from the RCER cloud."""
63
-
86
+ await self._validate_sharepoint_current_folders()
64
87
  try:
65
88
  cloud_files = set()
66
89
  async with self.sharepoint_client:
67
- for folder in c.SHAREPOINT_THIES_FOLDERS:
68
- args = SpListFilesArgs(
69
- folder_relative_url=f"{c.SHAREPOINT_BASE_URL}/{folder}"
70
- )
90
+ for folder_path in self.sharepoint_folders_path:
91
+ folder_name = folder_path.split("/")[-1]
92
+ relative_url = f"{self.sharepoint_base_url}/{folder_path}"
93
+ args = SpListFilesArgs(folder_relative_url=relative_url)
71
94
  response = await self.sharepoint_client.list_files(args)
72
95
  cloud_files.update(
73
- {f"{folder}_{item['Name']}" for item in response["value"]}
96
+ {f"{folder_name}_{item['Name']}" for item in response["value"]} # type: ignore
74
97
  )
75
98
  return cloud_files
76
- except ConnectionError as error:
99
+ except Exception as error:
77
100
  raise SharePointFetchingError(reason=error)
78
101
 
79
102
  async def fetch_thies_file_names(self) -> set[str]:
80
103
  """Fetch file names from the THIES FTP server."""
81
104
  try:
82
- avg_files = await self.thies_ftp_client.list_files(
83
- FtpListFilesArgs(path=c.FTP_SERVER_PATH_AVG_FILES)
84
- )
85
- ext_files = await self.thies_ftp_client.list_files(
86
- FtpListFilesArgs(path=c.FTP_SERVER_PATH_EXT_FILES)
87
- )
88
- return {f"AVG_{name}" for name in avg_files} | {
89
- f"EXT_{name}" for name in ext_files
90
- }
105
+ thies_files = set()
106
+ for folder_path in self.ftp_server_folders_path:
107
+ # AV for average, and EXT for extreme.
108
+ prefix = "AVG" if "AV" in folder_path else "EXT"
109
+ files = await self.thies_ftp_client.list_files(
110
+ FtpListFilesArgs(path=folder_path)
111
+ )
112
+ files_names = {f"{prefix}_{name}" for name in files}
113
+ thies_files.update(files_names)
114
+ return thies_files
91
115
  except ConnectionRefusedError as error:
92
116
  raise ThiesConnectionError(reason=error)
93
117
  except ConnectionAbortedError as error:
@@ -98,16 +122,19 @@ class UpdateThiesDataUseCase:
98
122
  try:
99
123
  content_files = {}
100
124
  for file in self.uploading:
101
- origin, filename = file.split("_", 1)
102
- file_path = (
103
- f"{c.FTP_SERVER_PATH_AVG_FILES}/{filename}"
104
- if origin == "AVG"
105
- else f"{c.FTP_SERVER_PATH_EXT_FILES}/{filename}"
125
+ _, filename = file.split("_", 1)
126
+ folder_path = (
127
+ self.ftp_server_folders_path[0]
128
+ if "AV" in self.ftp_server_folders_path[0] # Folder with AVG prefix
129
+ else self.ftp_server_folders_path[1] # Folder with EXT prefix
106
130
  )
131
+ file_path = f"{folder_path}/{filename}"
107
132
  content = await self.thies_ftp_client.read_file(
108
133
  FtpReadFileArgs(file_path)
109
134
  )
110
- content_files[file] = content # Save the file with its prefix
135
+ content_files[file] = (
136
+ content # Save file content with its original name.
137
+ )
111
138
  return content_files
112
139
  except ConnectionRefusedError as error:
113
140
  raise ThiesConnectionError(reason=error)
@@ -123,9 +150,16 @@ class UpdateThiesDataUseCase:
123
150
  async with self.sharepoint_client:
124
151
  for file, file_content in files.items():
125
152
  try:
126
- folder, file_name = file.split("_", 1)
153
+ _, file_name = file.split("_", 1)
154
+ # Could be AVG or EXT.
155
+ if file_name in self.sharepoint_folders_path[0]:
156
+ folder_path = self.sharepoint_folders_path[0]
157
+ else:
158
+ folder_path = self.sharepoint_folders_path[1]
159
+ relative_url = f"{self.sharepoint_base_url}/{folder_path}"
160
+
127
161
  args = SpUploadFileArgs(
128
- folder_relative_url=f"{c.SHAREPOINT_BASE_URL}/{folder}",
162
+ folder_relative_url=relative_url,
129
163
  file_content=file_content,
130
164
  file_name=file_name,
131
165
  )
@@ -145,16 +179,17 @@ class UpdateThiesDataUseCase:
145
179
 
146
180
  return upload_results
147
181
 
148
- async def execute(self) -> dict:
182
+ async def execute(self):
149
183
  """Synchronize data from the THIES Center to the cloud."""
150
184
  try:
151
185
  thies_files = await self.fetch_thies_file_names()
152
186
  except RuntimeError as error:
153
187
  raise FtpClientError(error)
188
+
154
189
  try:
155
190
  cloud_files = await self.fetch_cloud_file_names()
156
191
  except RuntimeError as error:
157
- raise SharepointClient(error)
192
+ raise SharepointClient(error) # type: ignore
158
193
 
159
194
  self.uploading = thies_files - cloud_files
160
195
  if not self.uploading:
@@ -168,4 +203,4 @@ class UpdateThiesDataUseCase:
168
203
  thies_fetched_files
169
204
  )
170
205
 
171
- return parse_execute_response(thies_fetched_files, upload_statistics)
206
+ return parse_execute_response(thies_fetched_files, upload_statistics) # type: ignore
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
2
  from time import time
3
3
  from logging import Logger
4
- import saviialib.services.epii.use_cases.constants.upload_backup_to_sharepoint_constants as c
5
4
  from saviialib.general_types.error_types.api.epii_api_error_types import (
6
5
  BackupEmptyError,
7
6
  BackupSourcePathError,
@@ -21,6 +20,7 @@ from saviialib.libs.sharepoint_client import (
21
20
  SharepointClient,
22
21
  SharepointClientInitArgs,
23
22
  SpUploadFileArgs,
23
+ SpListFoldersArgs,
24
24
  )
25
25
  from saviialib.services.epii.utils.upload_backup_to_sharepoint_utils import (
26
26
  calculate_percentage_uploaded,
@@ -39,13 +39,21 @@ class UploadBackupToSharepointUsecase:
39
39
  def __init__(self, input: UploadBackupToSharepointUseCaseInput):
40
40
  self.sharepoint_config = input.sharepoint_config
41
41
  self.local_backup_source_path = input.local_backup_source_path
42
- self.destination_folders = input.destination_folders
42
+ self.sharepoint_destination_path = input.sharepoint_destination_path
43
43
  self.files_client = self._initialize_files_client()
44
44
  self.dir_client = self._initialize_directory_client()
45
45
  self.log_history = []
46
46
  self.grouped_files_by_folder = None
47
47
  self.total_files = None
48
48
  self.logger: Logger = input.logger
49
+ self.sharepoint_client = self._initalize_sharepoint_client()
50
+
51
+ def _initalize_sharepoint_client(self):
52
+ return SharepointClient(
53
+ SharepointClientInitArgs(
54
+ self.sharepoint_config, client_name="sharepoint_rest_api"
55
+ )
56
+ )
49
57
 
50
58
  def _initialize_directory_client(self):
51
59
  return DirectoryClient(DirectoryClientArgs(client_name="os_client"))
@@ -53,7 +61,44 @@ class UploadBackupToSharepointUsecase:
53
61
  def _initialize_files_client(self):
54
62
  return FilesClient(FilesClientInitArgs(client_name="aiofiles_client"))
55
63
 
56
- async def _extract_filesnames_by_folder(self) -> dict[str, list[str]]:
64
+ async def _validate_backup_structure(self):
65
+ # Check if the local path exists in the main directory
66
+ if not await self.dir_client.path_exists(self.local_backup_source_path):
67
+ raise BackupSourcePathError(
68
+ reason=f"'{self.local_backup_source_path}' doesn't exist."
69
+ )
70
+
71
+ local_directories = (
72
+ list(self.grouped_files_by_folder.keys())
73
+ if self.grouped_files_by_folder
74
+ else []
75
+ )
76
+ 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
81
+
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}'."
90
+ )
91
+
92
+ # Check if the current folder only have files and each folder exist in Microsoft Sharepoint.
93
+ if self.total_files == 0:
94
+ no_files_message = (
95
+ f"[local_backup_lib] {self.local_backup_source_path} has no files ⚠️"
96
+ )
97
+ self.log_history.append(no_files_message)
98
+ self.logger.debug(no_files_message)
99
+ raise BackupEmptyError
100
+
101
+ async def _group_files_by_folder(self) -> dict[str, list[str]]:
57
102
  """Groups files by their parent folder."""
58
103
  backup_folder_exists = await self.dir_client.path_exists(
59
104
  self.local_backup_source_path
@@ -62,17 +107,17 @@ class UploadBackupToSharepointUsecase:
62
107
  if not backup_folder_exists:
63
108
  return {}
64
109
  folder_names = await self.dir_client.listdir(self.local_backup_source_path)
65
- return {
66
- folder_name: [
67
- file_name
68
- for file_name in await self.dir_client.listdir(
69
- self.dir_client.join_paths(
70
- self.local_backup_source_path, folder_name
71
- )
72
- )
73
- ]
74
- for folder_name in folder_names
75
- }
110
+ grouped = {}
111
+ for folder_name in folder_names:
112
+ is_folder = await self.dir_client.isdir(
113
+ self.dir_client.join_paths(self.local_backup_source_path, folder_name)
114
+ )
115
+ if not is_folder:
116
+ continue
117
+ grouped[folder_name] = await self.dir_client.listdir(
118
+ self.dir_client.join_paths(self.local_backup_source_path, folder_name)
119
+ )
120
+ return grouped
76
121
 
77
122
  async def _save_log_history(self) -> None:
78
123
  await self.files_client.write(
@@ -83,6 +128,30 @@ class UploadBackupToSharepointUsecase:
83
128
  )
84
129
  )
85
130
 
131
+ async def _generate_tasks(self) -> list:
132
+ tasks = []
133
+ for folder_name in self.grouped_files_by_folder: # type: ignore
134
+ count_files_in_dir = await count_files_in_directory(
135
+ self.local_backup_source_path, folder_name
136
+ )
137
+ if count_files_in_dir == 0:
138
+ empty_folder_message = (
139
+ f"[local_backup_lib] The folder '{folder_name}' is empty ⚠️"
140
+ )
141
+ self.logger.debug(empty_folder_message)
142
+ self.log_history.append(empty_folder_message)
143
+ continue
144
+ extracting_files_message = (
145
+ "[local_backup_lib]"
146
+ + f" Extracting files from '{folder_name} ".center(15, "*")
147
+ )
148
+ self.log_history.append(extracting_files_message)
149
+ self.logger.debug(extracting_files_message)
150
+ for file_name in self.grouped_files_by_folder[folder_name]: # type: ignore
151
+ tasks.append(self._upload_and_log_progress_task(folder_name, file_name))
152
+
153
+ return tasks
154
+
86
155
  async def export_file_to_sharepoint(
87
156
  self, folder_name: str, file_name: str, file_content: bytes
88
157
  ) -> tuple[bool, str]:
@@ -101,10 +170,7 @@ class UploadBackupToSharepointUsecase:
101
170
 
102
171
  async with sharepoint_client:
103
172
  try:
104
- destination_folder = self.destination_folders.get(
105
- folder_name, folder_name
106
- )
107
- folder_url = f"{c.SHAREPOINT_BASE_URL}/{destination_folder}"
173
+ folder_url = f"{self.sharepoint_destination_path}/{folder_name}"
108
174
  args = SpUploadFileArgs(
109
175
  folder_relative_url=folder_url,
110
176
  file_content=file_content,
@@ -130,7 +196,9 @@ class UploadBackupToSharepointUsecase:
130
196
  )
131
197
  file_content = await self.files_client.read(ReadArgs(file_path, mode="rb"))
132
198
  uploaded, error_message = await self.export_file_to_sharepoint(
133
- folder_name, file_name, file_content
199
+ folder_name,
200
+ file_name,
201
+ file_content, # type: ignore
134
202
  )
135
203
  result_message = show_upload_result(uploaded, file_name)
136
204
  self.logger.debug(result_message)
@@ -155,10 +223,10 @@ class UploadBackupToSharepointUsecase:
155
223
  )
156
224
  )
157
225
  results = await asyncio.gather(*tasks, return_exceptions=True)
158
- success = calculate_percentage_uploaded(results, self.total_files)
226
+ success = calculate_percentage_uploaded(results, self.total_files) # type: ignore
159
227
  if success < 100.0:
160
228
  raise BackupUploadError(
161
- reason=extract_error_message(self.logger, results, success)
229
+ reason=extract_error_message(self.logger, results, success) # type: ignore
162
230
  )
163
231
  else:
164
232
  successful_upload_retry = (
@@ -167,77 +235,26 @@ class UploadBackupToSharepointUsecase:
167
235
  self.log_history.append(successful_upload_retry)
168
236
  self.logger.debug(successful_upload_retry)
169
237
  await self._save_log_history()
170
- return parse_execute_response(results)
238
+ return parse_execute_response(results) # type: ignore
171
239
 
172
240
  async def execute(self):
173
241
  """Exports all files from the local backup folder to SharePoint cloud."""
174
- self.grouped_files_by_folder = await self._extract_filesnames_by_folder()
242
+ start_time = time()
243
+ self.grouped_files_by_folder = await self._group_files_by_folder()
175
244
  self.total_files = sum(
176
245
  len(files) for files in self.grouped_files_by_folder.values()
177
246
  )
178
- tasks = []
179
- start_time = time()
180
247
 
181
- # Check if the local path exists in the main directory
182
- if not await self.dir_client.path_exists(self.local_backup_source_path):
183
- raise BackupSourcePathError(
184
- reason=f"'{self.local_backup_source_path}' doesn't exist."
185
- )
186
-
187
- # Check if the current folder only have files.
188
- items = [
189
- item
190
- for item in await self.dir_client.listdir(self.local_backup_source_path)
191
- ]
192
- for item in items:
193
- folder_included = item in self.destination_folders.keys()
194
- is_file = not await self.dir_client.isdir(
195
- self.dir_client.join_paths(self.local_backup_source_path, item)
196
- )
248
+ # Check if the current folder only have files and each folder exist in Microsoft Sharepoint.
249
+ await self._validate_backup_structure()
197
250
 
198
- if not folder_included and not is_file:
199
- raise BackupSourcePathError(
200
- reason=(
201
- f"'{item}' must be included in the destination folders dictionary",
202
- )
203
- )
204
- elif folder_included and is_file:
205
- print(folder_included, is_file)
206
- raise BackupSourcePathError(reason=(f"'{item}' must be a directory.",))
207
-
208
- if self.total_files == 0:
209
- no_files_message = (
210
- f"[local_backup_lib] {self.local_backup_source_path} has no files ⚠️"
211
- )
212
- self.log_history.append(no_files_message)
213
- self.logger.debug(no_files_message)
214
- raise BackupEmptyError
215
251
  # Create task for each file stored in the the local backup folder.
216
- for folder_name in self.grouped_files_by_folder:
217
- if (
218
- await count_files_in_directory(
219
- self.local_backup_source_path, folder_name
220
- )
221
- == 0
222
- ):
223
- empty_folder_message = (
224
- f"[local_backup_lib] The folder '{folder_name}' is empty ⚠️"
225
- )
226
- self.logger.debug(empty_folder_message)
227
- self.log_history.append(empty_folder_message)
228
- continue
229
- extracting_files_message = (
230
- "[local_backup_lib]"
231
- + f" Extracting files from '{folder_name} ".center(15, "*")
232
- )
233
- self.log_history.append(extracting_files_message)
234
- self.logger.debug(extracting_files_message)
235
- for file_name in self.grouped_files_by_folder[folder_name]:
236
- tasks.append(self._upload_and_log_progress_task(folder_name, file_name))
252
+ tasks = await self._generate_tasks()
237
253
 
238
254
  # Execution of multiple asynchronous tasks for files migration.
239
255
  results = await asyncio.gather(*tasks, return_exceptions=True)
240
- success = calculate_percentage_uploaded(results, self.total_files)
256
+ success = calculate_percentage_uploaded(results, self.total_files) # type: ignore
257
+
241
258
  if success < 100.0:
242
259
  await self.retry_upload_failed_files(results)
243
260
  else:
@@ -254,4 +271,4 @@ class UploadBackupToSharepointUsecase:
254
271
  self.log_history.append(finished_backup_message)
255
272
 
256
273
  await self._save_log_history()
257
- return parse_execute_response(results)
274
+ return parse_execute_response(results) # type: ignore
@@ -1,5 +0,0 @@
1
- SHAREPOINT_BASE_URL = "/sites/uc365_CentrosyEstacionesRegionalesUC/Shared%20Documents/General/Test_Raspberry/THIES"
2
- SHAREPOINT_THIES_FOLDERS = ["AVG", "EXT"]
3
-
4
- FTP_SERVER_PATH_AVG_FILES = "ftp/thies/BINFILES/ARCH_AV1"
5
- FTP_SERVER_PATH_EXT_FILES = "ftp/thies/BINFILES/ARCH_EX1"
@@ -1,6 +0,0 @@
1
- import logging
2
-
3
- SHAREPOINT_BASE_URL = "/sites/uc365_CentrosyEstacionesRegionalesUC/Shared%20Documents/General/Test_Raspberry"
4
-
5
- logging.basicConfig(level=logging.DEBUG)
6
- LOGGER = logging.getLogger(__package__)
File without changes
File without changes