saviialib 0.6.2__tar.gz → 0.8.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.
Files changed (53) hide show
  1. {saviialib-0.6.2 → saviialib-0.8.0}/PKG-INFO +23 -24
  2. {saviialib-0.6.2 → saviialib-0.8.0}/README.md +21 -23
  3. {saviialib-0.6.2 → saviialib-0.8.0}/pyproject.toml +2 -1
  4. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/__init__.py +2 -2
  5. saviialib-0.8.0/src/saviialib/general_types/api/__init__.py +3 -0
  6. saviialib-0.8.0/src/saviialib/general_types/api/epii_api_types.py +81 -0
  7. saviialib-0.6.2/src/saviialib/general_types/error_types/api/update_thies_data_error_types.py → saviialib-0.8.0/src/saviialib/general_types/error_types/api/epii_api_error_types.py +36 -0
  8. saviialib-0.8.0/src/saviialib/libs/files_client/__init__.py +4 -0
  9. saviialib-0.8.0/src/saviialib/libs/files_client/clients/aiofiles_client.py +21 -0
  10. saviialib-0.8.0/src/saviialib/libs/files_client/files_client.py +28 -0
  11. saviialib-0.8.0/src/saviialib/libs/files_client/files_client_contract.py +13 -0
  12. saviialib-0.8.0/src/saviialib/libs/files_client/types/files_client_types.py +31 -0
  13. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/zero_dependency/utils/datetime_utils.py +2 -2
  14. saviialib-0.8.0/src/saviialib/services/epii/api.py +79 -0
  15. saviialib-0.8.0/src/saviialib/services/epii/controllers/__init__.py +0 -0
  16. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/services/epii/controllers/types/update_thies_data_types.py +1 -1
  17. saviialib-0.8.0/src/saviialib/services/epii/controllers/types/upload_backup_to_sharepoint_types.py +16 -0
  18. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/services/epii/controllers/update_thies_data.py +1 -1
  19. saviialib-0.8.0/src/saviialib/services/epii/controllers/upload_backup_to_sharepoint.py +85 -0
  20. saviialib-0.8.0/src/saviialib/services/epii/use_cases/constants/upload_backup_to_sharepoint_constants.py +5 -0
  21. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/services/epii/use_cases/types/update_thies_data_types.py +1 -17
  22. saviialib-0.8.0/src/saviialib/services/epii/use_cases/types/upload_backup_to_sharepoint_types.py +8 -0
  23. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/services/epii/use_cases/update_thies_data.py +5 -13
  24. saviialib-0.8.0/src/saviialib/services/epii/use_cases/upload_backup_to_sharepoint.py +201 -0
  25. saviialib-0.8.0/src/saviialib/services/epii/utils/upload_backup_to_sharepoint_utils.py +92 -0
  26. saviialib-0.6.2/src/saviialib/general_types/api/__init__.py +0 -3
  27. saviialib-0.6.2/src/saviialib/general_types/api/update_thies_data_types.py +0 -30
  28. saviialib-0.6.2/src/saviialib/services/epii/api.py +0 -26
  29. saviialib-0.6.2/src/saviialib/services/epii/controllers/__init__.py +0 -3
  30. {saviialib-0.6.2 → saviialib-0.8.0}/LICENSE +0 -0
  31. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/general_types/__init__.py +0 -0
  32. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/general_types/error_types/__init__.py +0 -0
  33. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/general_types/error_types/api/__init__.py +0 -0
  34. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/general_types/error_types/common/__init__.py +0 -0
  35. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/general_types/error_types/common/common_types.py +0 -0
  36. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/ftp_client/__init__.py +0 -0
  37. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/ftp_client/clients/__init__.py +0 -0
  38. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/ftp_client/clients/aioftp_client.py +0 -0
  39. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/ftp_client/ftp_client.py +0 -0
  40. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/ftp_client/ftp_client_contract.py +0 -0
  41. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/ftp_client/types/__init__.py +0 -0
  42. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/ftp_client/types/ftp_client_types.py +0 -0
  43. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/sharepoint_client/__init__.py +0 -0
  44. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py +0 -0
  45. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/sharepoint_client/sharepoint_client.py +0 -0
  46. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/sharepoint_client/sharepoint_client_contract.py +0 -0
  47. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/libs/sharepoint_client/types/sharepoint_client_types.py +0 -0
  48. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/services/epii/__init__.py +0 -0
  49. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/services/epii/controllers/types/__init__.py +0 -0
  50. {saviialib-0.6.2/src/saviialib/services/epii → saviialib-0.8.0/src/saviialib/services/epii/use_cases}/constants/update_thies_data_constants.py +0 -0
  51. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/services/epii/use_cases/types/__init__.py +0 -0
  52. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/services/epii/utils/__init__.py +0 -0
  53. {saviialib-0.6.2 → saviialib-0.8.0}/src/saviialib/services/epii/utils/update_thies_data_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: saviialib
3
- Version: 0.6.2
3
+ Version: 0.8.0
4
4
  Summary: A client library for IoT projects in the RCER initiative
5
5
  License: MIT
6
6
  Author: pedropablozavalat
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: aiofiles (==24.1.0)
14
15
  Requires-Dist: aioftp (==0.25.1)
15
16
  Requires-Dist: aiohttp (==3.11.16)
16
17
  Requires-Dist: build
@@ -34,23 +35,11 @@ pip install saviialib
34
35
  ## Usage
35
36
 
36
37
  ### Initialize the EPii API Client
37
- To start using the library, you need to create an `EpiiAPI` client instance:
38
+ To start using the library, you need to create an `EpiiAPI` client instance with its configuration class:
38
39
 
39
40
  ```python
40
- from saviialib import EpiiAPI
41
-
42
- api_client = EpiiAPI()
43
- ```
44
-
45
- ### Update THIES Data Logger Files
46
- The library provides a method to synchronize THIES Data Logger files with the RCER SharePoint client. This method updates the folder containing binary files with meteorological data:
47
-
48
- ```python
49
- from saviialib import EpiiUpdateThiesConfig
50
- import asyncio
51
-
52
- async def update_thies_data():
53
- config = EpiiUpdateThiesConfig(
41
+ from saviialib import EpiiAPI, EpiiAPIConfig
42
+ config = EpiiAPIConfig(
54
43
  ftp_port=FTP_PORT,
55
44
  ftp_host=FTP_HOST,
56
45
  ftp_user=FTP_USER,
@@ -61,15 +50,26 @@ async def update_thies_data():
61
50
  sharepoint_tenant_name=SHAREPOINT_TENANT_NAME,
62
51
  sharepoint_site_name=SHAREPOINT_SITE_NAME
63
52
  )
64
- response = await api_client.update_thies_data(config)
65
- return response
66
-
67
- asyncio.run(update_thies_data())
53
+ api_client = EpiiAPI(config)
68
54
  ```
69
-
70
55
  **Notes:**
71
56
  - 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.
72
57
 
58
+ ### Update THIES Data Logger Files
59
+ The library provides a method to synchronize THIES Data Logger files with the RCER SharePoint client. This method updates the folder containing binary files with meteorological data:
60
+
61
+ ```python
62
+ from saviialib import EpiiUpdateThiesConfig
63
+ import asyncio
64
+
65
+ async def main():
66
+ # Before calling this method, you must have initialised the api class ...
67
+ response = await api_client.update_thies_data()
68
+ return response
69
+
70
+ asyncio.run(main())
71
+ ```
72
+
73
73
  ## Development
74
74
 
75
75
  This project includes a `Makefile` to simplify common tasks. Below are the available commands:
@@ -111,9 +111,8 @@ make lint
111
111
  This command checks the codebase for linting issues and outputs any problems that need to be addressed.
112
112
 
113
113
  ## Contributing
114
- If you're interested in contributing to this project, please follow the contributing guidelines. Contributions are welcome and appreciated!
115
-
116
- Interested in contributing? Check out the contributing guidelines. Please note that this project is released with a Code of Conduct. By contributing to this project, you agree to abide by its terms.
114
+ 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.
115
+ Contributions are welcome and appreciated!
117
116
 
118
117
  ## License
119
118
 
@@ -14,23 +14,11 @@ pip install saviialib
14
14
  ## Usage
15
15
 
16
16
  ### Initialize the EPii API Client
17
- To start using the library, you need to create an `EpiiAPI` client instance:
17
+ To start using the library, you need to create an `EpiiAPI` client instance with its configuration class:
18
18
 
19
19
  ```python
20
- from saviialib import EpiiAPI
21
-
22
- api_client = EpiiAPI()
23
- ```
24
-
25
- ### Update THIES Data Logger Files
26
- The library provides a method to synchronize THIES Data Logger files with the RCER SharePoint client. This method updates the folder containing binary files with meteorological data:
27
-
28
- ```python
29
- from saviialib import EpiiUpdateThiesConfig
30
- import asyncio
31
-
32
- async def update_thies_data():
33
- config = EpiiUpdateThiesConfig(
20
+ from saviialib import EpiiAPI, EpiiAPIConfig
21
+ config = EpiiAPIConfig(
34
22
  ftp_port=FTP_PORT,
35
23
  ftp_host=FTP_HOST,
36
24
  ftp_user=FTP_USER,
@@ -41,15 +29,26 @@ async def update_thies_data():
41
29
  sharepoint_tenant_name=SHAREPOINT_TENANT_NAME,
42
30
  sharepoint_site_name=SHAREPOINT_SITE_NAME
43
31
  )
44
- response = await api_client.update_thies_data(config)
45
- return response
46
-
47
- asyncio.run(update_thies_data())
32
+ api_client = EpiiAPI(config)
48
33
  ```
49
-
50
34
  **Notes:**
51
35
  - 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.
52
36
 
37
+ ### Update THIES Data Logger Files
38
+ The library provides a method to synchronize THIES Data Logger files with the RCER SharePoint client. This method updates the folder containing binary files with meteorological data:
39
+
40
+ ```python
41
+ from saviialib import EpiiUpdateThiesConfig
42
+ import asyncio
43
+
44
+ async def main():
45
+ # Before calling this method, you must have initialised the api class ...
46
+ response = await api_client.update_thies_data()
47
+ return response
48
+
49
+ asyncio.run(main())
50
+ ```
51
+
53
52
  ## Development
54
53
 
55
54
  This project includes a `Makefile` to simplify common tasks. Below are the available commands:
@@ -91,9 +90,8 @@ make lint
91
90
  This command checks the codebase for linting issues and outputs any problems that need to be addressed.
92
91
 
93
92
  ## Contributing
94
- If you're interested in contributing to this project, please follow the contributing guidelines. Contributions are welcome and appreciated!
95
-
96
- Interested in contributing? Check out the contributing guidelines. Please note that this project is released with a Code of Conduct. By contributing to this project, you agree to abide by its terms.
93
+ 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.
94
+ Contributions are welcome and appreciated!
97
95
 
98
96
  ## License
99
97
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "saviialib"
3
- version = "0.6.2"
3
+ version = "0.8.0"
4
4
  description = "A client library for IoT projects in the RCER initiative"
5
5
  authors = ["pedropablozavalat"]
6
6
  license = "MIT"
@@ -10,6 +10,7 @@ readme = "README.md"
10
10
  python = "^3.10"
11
11
  aioftp = "0.25.1"
12
12
  aiohttp = "3.11.16"
13
+ aiofiles = "24.1.0"
13
14
  dotenv = "0.9.9"
14
15
  pytest-cov="6.1.1"
15
16
  build="*"
@@ -4,6 +4,6 @@ from importlib.metadata import version
4
4
  __version__ = version("saviialib")
5
5
 
6
6
  from .services.epii.api import EpiiAPI
7
- from .general_types.api.update_thies_data_types import EpiiUpdateThiesConfig
7
+ from .general_types.api.epii_api_types import EpiiAPIConfig
8
8
 
9
- __all__ = ["EpiiAPI", "EpiiUpdateThiesConfig"]
9
+ __all__ = ["EpiiAPI", "EpiiAPIConfig"]
@@ -0,0 +1,3 @@
1
+ from .epii_api_types import EpiiUpdateThiesConfig, EpiiSharepointBackupConfig
2
+
3
+ __all__ = ["EpiiUpdateThiesConfig", "EpiiSharepointBackupConfig"]
@@ -0,0 +1,81 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class EpiiAPIConfig:
6
+ """
7
+ Configuration for Epii API.
8
+
9
+ Attributes:
10
+ ftp_port (int): Port number of the FTP server.
11
+ ftp_host (str): Hostname or IP address of the FTP server.
12
+ ftp_user (str): Username for the FTP server.
13
+ ftp_password (str): Password for the FTP server.
14
+ sharepoint_client_id (str): Client ID for SharePoint authentication.
15
+ sharepoint_client_secret (str): Client secret for SharePoint authentication.
16
+ sharepoint_tenant_id (str): Tenant ID for SharePoint authentication.
17
+ sharepoint_tenant_name (str): Tenant name for SharePoint.
18
+ sharepoint_site_name (str): Site name in SharePoint.
19
+ """
20
+
21
+ ftp_host: str
22
+ ftp_port: int
23
+ ftp_user: str
24
+ ftp_password: str
25
+ sharepoint_client_id: str
26
+ sharepoint_client_secret: str
27
+ sharepoint_tenant_id: str
28
+ sharepoint_tenant_name: str
29
+ sharepoint_site_name: str
30
+
31
+
32
+ @dataclass
33
+ class FtpClientConfig:
34
+ ftp_host: str
35
+ ftp_port: int
36
+ ftp_user: str
37
+ ftp_password: str
38
+
39
+
40
+ @dataclass
41
+ class SharepointConfig:
42
+ sharepoint_client_id: str
43
+ sharepoint_client_secret: str
44
+ sharepoint_tenant_id: str
45
+ sharepoint_tenant_name: str
46
+ sharepoint_site_name: str
47
+
48
+
49
+ @dataclass
50
+ class EpiiUpdateThiesConfig:
51
+ ftp_port: int
52
+ ftp_host: str
53
+ ftp_user: str
54
+ ftp_password: str
55
+ sharepoint_client_id: str
56
+ sharepoint_client_secret: str
57
+ sharepoint_tenant_id: str
58
+ sharepoint_tenant_name: str
59
+ sharepoint_site_name: str
60
+
61
+
62
+ @dataclass
63
+ class EpiiSharepointBackupConfig:
64
+ """
65
+ Configuration for backing up files to SharePoint.
66
+
67
+ Attributes:
68
+ sharepoint_client_id (str): Client ID for SharePoint authentication.
69
+ sharepoint_client_secret (str): Client secret for SharePoint authentication.
70
+ sharepoint_tenant_id (str): Tenant ID for SharePoint authentication.
71
+ sharepoint_tenant_name (str): Tenant name for SharePoint.
72
+ sharepoint_site_name (str): Site name in SharePoint.
73
+ local_backup_source_path (str): Local path to backup.
74
+ """
75
+
76
+ sharepoint_client_id: str
77
+ sharepoint_client_secret: str
78
+ sharepoint_tenant_id: str
79
+ sharepoint_tenant_name: str
80
+ sharepoint_site_name: str
81
+ local_backup_source_path: str
@@ -55,3 +55,39 @@ class SharePointUploadError(Exception):
55
55
  "An error occurred while uploading files to the Microsoft SharePoint folder. "
56
56
  + self.reason.__str__()
57
57
  )
58
+
59
+
60
+ class BackupUploadError(Exception):
61
+ """Raised when there is an error when occurs the migration from the local backup to
62
+ sharepoint cloud."""
63
+
64
+ def __init__(self, *args, reason):
65
+ super().__init__(*args, reason)
66
+ self.reason = reason
67
+
68
+ def __str__(self):
69
+ return (
70
+ "An error occurred during the migration from the local backup to SharePoint cloud. "
71
+ "Search the logs for more information. " + self.reason.__str__()
72
+ )
73
+
74
+
75
+ class BackupSourcePathError(Exception):
76
+ """Raised when the local backup source path is invalid."""
77
+
78
+ def __init__(self, *args, reason):
79
+ super().__init__(*args, reason)
80
+ self.reason = reason
81
+
82
+ def __str__(self):
83
+ return "Invalid local backup source path. " + self.reason.__str__()
84
+
85
+
86
+ class BackupEmptyError(Exception):
87
+ """Raised when the local backup folder is empty."""
88
+
89
+ def __init__(self, *args):
90
+ super().__init__(*args)
91
+
92
+ def __str__(self):
93
+ return "The local backup folder is empty. "
@@ -0,0 +1,4 @@
1
+ from .files_client import FilesClient
2
+ from .types.files_client_types import FilesClientInitArgs, ReadArgs
3
+
4
+ __all__ = ["FilesClient", "FilesClientInitArgs", "ReadArgs"]
@@ -0,0 +1,21 @@
1
+ import aiofiles
2
+
3
+ from saviialib.libs.files_client.files_client_contract import FilesClientContract
4
+ from saviialib.libs.files_client.types.files_client_types import (
5
+ FilesClientInitArgs,
6
+ ReadArgs,
7
+ WriteArgs,
8
+ )
9
+
10
+
11
+ class AioFilesClient(FilesClientContract):
12
+ def __init__(self, args: FilesClientInitArgs):
13
+ pass
14
+
15
+ async def read(self, args: ReadArgs) -> str | bytes:
16
+ encoding = None if args.mode == "rb" else args.encoding
17
+ async with aiofiles.open(args.file_path, args.mode, encoding=encoding) as file:
18
+ return await file.read()
19
+
20
+ async def write(self, args: WriteArgs) -> None:
21
+ return None
@@ -0,0 +1,28 @@
1
+ from .clients.aiofiles_client import AioFilesClient
2
+ from .files_client_contract import FilesClientContract
3
+ from .types.files_client_types import FilesClientInitArgs, ReadArgs, WriteArgs
4
+
5
+
6
+ class FilesClient(FilesClientContract):
7
+ CLIENTS = {"aiofiles_client"}
8
+
9
+ def __init__(self, args: FilesClientInitArgs) -> None:
10
+ if args.client_name not in FilesClient.CLIENTS:
11
+ msg = f"Unsupported client {args.client_name}"
12
+ raise KeyError(msg)
13
+
14
+ if args.client_name == "aiofiles_client":
15
+ self.client_obj = AioFilesClient(args)
16
+ self.client_name = args.client_name
17
+
18
+ async def read(self, args: ReadArgs):
19
+ """Reads data from a specified source using the provided arguments.
20
+
21
+ :param args (ReadArgs): An object containing the parameters required for the read operation.
22
+
23
+ :return file: The result of the read operation, as returned by the client object.
24
+ """
25
+ return await self.client_obj.read(args)
26
+
27
+ async def write(self, args: WriteArgs):
28
+ return await self.client_obj.write(args)
@@ -0,0 +1,13 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from .types.files_client_types import ReadArgs, WriteArgs
4
+
5
+
6
+ class FilesClientContract(ABC):
7
+ @abstractmethod
8
+ async def read(self, args: ReadArgs) -> str | bytes:
9
+ pass
10
+
11
+ @abstractmethod
12
+ async def write(self, arg: WriteArgs) -> None:
13
+ pass
@@ -0,0 +1,31 @@
1
+ from dataclasses import dataclass
2
+ from typing import Literal
3
+
4
+
5
+ @dataclass
6
+ class FilesClientInitArgs:
7
+ client_name: str = "aiofiles_client"
8
+
9
+
10
+ @dataclass
11
+ class ReadArgs:
12
+ """
13
+ Represents the arguments required to read a file.
14
+
15
+ Attributes:
16
+ file_path (str): The path to the file to be read.
17
+ mode (Literal['r', 'rb']): The mode in which the file should be opened.
18
+ - 'r': Open for reading (text mode).
19
+ - 'rb': Open for reading (binary mode).
20
+ """
21
+
22
+ file_path: str
23
+ mode: Literal["r", "rb"]
24
+ encoding: str = "utf-8"
25
+
26
+
27
+ @dataclass
28
+ class WriteArgs:
29
+ destination_path: str
30
+ file_name: str
31
+ file_content: str | bytes
@@ -13,13 +13,13 @@ def today(timezone: str = "America/Santiago") -> str:
13
13
  return datetime.now(tz=ZoneInfo(timezone))
14
14
 
15
15
 
16
- def datetime_to_str(date: datetime, date_format: str = "%Y-%m-%dT%H:%M%:S") -> str:
16
+ def datetime_to_str(date: datetime, date_format: str = "%m/%d/%Y, %H:%M:%S") -> str:
17
17
  """
18
18
  Convert a datetime object to a string in the specified format.
19
19
 
20
20
  :param date: The datetime object to convert.
21
21
  :param date_format: The format to convert the datetime object to.
22
- Defaults to "YYYYMMDD:HHMMSS".
22
+ Defaults to "%Y-%m-%dT%H:%M:%S".
23
23
  :return: A string in the specified format.
24
24
  """
25
25
  return date.strftime(date_format)
@@ -0,0 +1,79 @@
1
+ from typing import Any, Dict
2
+
3
+ from .controllers.types.update_thies_data_types import UpdateThiesDataControllerInput
4
+ from .controllers.types.upload_backup_to_sharepoint_types import (
5
+ UploadBackupToSharepointControllerInput,
6
+ )
7
+ from .controllers.update_thies_data import UpdateThiesDataController
8
+ from .controllers.upload_backup_to_sharepoint import UploadBackupToSharepointController
9
+ from saviialib.general_types.api.epii_api_types import (
10
+ EpiiUpdateThiesConfig,
11
+ EpiiSharepointBackupConfig,
12
+ EpiiAPIConfig,
13
+ )
14
+
15
+
16
+ class EpiiAPI:
17
+ """
18
+ EpiiAPI is a service class that provides methods to interact with Patagonia Center system.
19
+ """
20
+
21
+ def __init__(self, config: EpiiAPIConfig):
22
+ self.ftp_port = config.ftp_port
23
+ self.ftp_host = config.ftp_host
24
+ self.ftp_user = config.ftp_user
25
+ self.ftp_password = config.ftp_password
26
+ self.sharepoint_client_id = config.sharepoint_client_id
27
+ self.sharepoint_client_secret = config.sharepoint_client_secret
28
+ self.sharepoint_tenant_id = config.sharepoint_tenant_id
29
+ self.sharepoint_tenant_name = config.sharepoint_tenant_name
30
+ self.sharepoint_site_name = config.sharepoint_site_name
31
+
32
+ async def update_thies_data(self) -> Dict[str, Any]:
33
+ """
34
+ This method establishes a connection to an FTP server using the provided
35
+ credentials and updates data related to THIES Data Logger.
36
+
37
+ Returns:
38
+ response (dict): A dictionary representation of the API response.
39
+ """
40
+ config = EpiiUpdateThiesConfig(
41
+ ftp_port=self.ftp_port,
42
+ ftp_host=self.ftp_host,
43
+ ftp_user=self.ftp_user,
44
+ ftp_password=self.ftp_password,
45
+ sharepoint_client_id=self.sharepoint_client_id,
46
+ sharepoint_client_secret=self.sharepoint_client_secret,
47
+ sharepoint_site_name=self.sharepoint_site_name,
48
+ sharepoint_tenant_id=self.sharepoint_tenant_id,
49
+ sharepoint_tenant_name=self.sharepoint_tenant_name,
50
+ )
51
+ controller = UpdateThiesDataController(UpdateThiesDataControllerInput(config))
52
+ response = await controller.execute()
53
+ return response.__dict__
54
+
55
+ async def upload_backup_to_sharepoint(
56
+ self, local_backup_source_path: str
57
+ ) -> Dict[str, Any]:
58
+ """Migrate a backup folder from Home assistant to Sharepoint directory.
59
+ Args:
60
+ local_backup_source_path (str): Local path to backup.
61
+ Returns:
62
+ response (dict): A dictionary containing the response from the upload operation.
63
+ This dictionary will typically include information about the success or
64
+ failure of the upload, as well as any relevant metadata.
65
+ """
66
+ config = EpiiSharepointBackupConfig(
67
+ sharepoint_client_id=self.sharepoint_client_id,
68
+ sharepoint_client_secret=self.sharepoint_client_secret,
69
+ sharepoint_site_name=self.sharepoint_site_name,
70
+ sharepoint_tenant_id=self.sharepoint_tenant_id,
71
+ sharepoint_tenant_name=self.sharepoint_tenant_name,
72
+ local_backup_source_path=local_backup_source_path,
73
+ )
74
+
75
+ controller = UploadBackupToSharepointController(
76
+ UploadBackupToSharepointControllerInput(config)
77
+ )
78
+ response = await controller.execute()
79
+ return response.__dict__
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import Dict
3
- from saviialib.general_types.api.update_thies_data_types import (
3
+ from saviialib.general_types.api.epii_api_types import (
4
4
  EpiiUpdateThiesConfig,
5
5
  )
6
6
 
@@ -0,0 +1,16 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Dict
3
+
4
+ from saviialib.general_types.api.epii_api_types import EpiiSharepointBackupConfig
5
+
6
+
7
+ @dataclass
8
+ class UploadBackupToSharepointControllerInput:
9
+ config: EpiiSharepointBackupConfig
10
+
11
+
12
+ @dataclass
13
+ class UploadBackupToSharepointControllerOutput:
14
+ message: str
15
+ status: int
16
+ metadata: Dict[str, str] = field(default_factory=dict)
@@ -1,6 +1,6 @@
1
1
  from http import HTTPStatus
2
2
 
3
- from saviialib.general_types.error_types.api.update_thies_data_error_types import (
3
+ from saviialib.general_types.error_types.api.epii_api_error_types import (
4
4
  SharePointFetchingError,
5
5
  ThiesConnectionError,
6
6
  ThiesFetchingError,
@@ -0,0 +1,85 @@
1
+ from http import HTTPStatus
2
+ from saviialib.general_types.api.epii_api_types import SharepointConfig
3
+
4
+ from saviialib.general_types.error_types.api.epii_api_error_types import (
5
+ BackupUploadError,
6
+ BackupSourcePathError,
7
+ BackupEmptyError,
8
+ )
9
+ from saviialib.general_types.error_types.common.common_types import (
10
+ EmptyDataError,
11
+ SharepointClientError,
12
+ )
13
+ from saviialib.services.epii.controllers.types.upload_backup_to_sharepoint_types import (
14
+ UploadBackupToSharepointControllerInput,
15
+ UploadBackupToSharepointControllerOutput,
16
+ )
17
+ from saviialib.services.epii.use_cases.types.upload_backup_to_sharepoint_types import (
18
+ UploadBackupToSharepointUseCaseInput,
19
+ )
20
+ from saviialib.services.epii.use_cases.upload_backup_to_sharepoint import (
21
+ UploadBackupToSharepointUsecase,
22
+ )
23
+
24
+
25
+ class UploadBackupToSharepointController:
26
+ def __init__(self, input: UploadBackupToSharepointControllerInput):
27
+ self.use_case = UploadBackupToSharepointUsecase(
28
+ UploadBackupToSharepointUseCaseInput(
29
+ sharepoint_config=SharepointConfig(
30
+ sharepoint_client_id=input.config.sharepoint_client_id,
31
+ sharepoint_client_secret=input.config.sharepoint_client_secret,
32
+ sharepoint_site_name=input.config.sharepoint_site_name,
33
+ sharepoint_tenant_name=input.config.sharepoint_tenant_name,
34
+ sharepoint_tenant_id=input.config.sharepoint_tenant_id,
35
+ ),
36
+ local_backup_source_path=input.config.local_backup_source_path,
37
+ )
38
+ )
39
+
40
+ async def execute(self) -> UploadBackupToSharepointControllerOutput:
41
+ try:
42
+ data = await self.use_case.execute()
43
+ return UploadBackupToSharepointControllerOutput(
44
+ message="Local backup was migrated successfully",
45
+ status=HTTPStatus.OK.value,
46
+ metadata={"data": data},
47
+ )
48
+ except EmptyDataError:
49
+ return UploadBackupToSharepointControllerOutput(
50
+ message="No files to upload", status=HTTPStatus.NO_CONTENT.value
51
+ )
52
+
53
+ except (AttributeError, NameError, ValueError) as error:
54
+ return UploadBackupToSharepointControllerOutput(
55
+ message="An unexpected error occurred during use case initialization.",
56
+ status=HTTPStatus.BAD_REQUEST.value,
57
+ metadata={"error": error.__str__()},
58
+ )
59
+
60
+ except SharepointClientError as error:
61
+ return UploadBackupToSharepointControllerOutput(
62
+ message="Sharepoint Client initialization fails.",
63
+ status=HTTPStatus.INTERNAL_SERVER_ERROR.value,
64
+ metadata={"error": error.__str__()},
65
+ )
66
+
67
+ except BackupUploadError as error:
68
+ return UploadBackupToSharepointControllerOutput(
69
+ message="An error occurred during local backup",
70
+ status=HTTPStatus.MULTI_STATUS.value,
71
+ metadata={"error": error.__str__()},
72
+ )
73
+
74
+ except BackupSourcePathError as error:
75
+ return UploadBackupToSharepointControllerOutput(
76
+ message="Invalid local backup source path provided.",
77
+ status=HTTPStatus.BAD_REQUEST.value,
78
+ metadata={"error": error.__str__()},
79
+ )
80
+ except BackupEmptyError as error:
81
+ return UploadBackupToSharepointControllerOutput(
82
+ message="Each folder in the backup folder is empty. Check out again",
83
+ status=HTTPStatus.EXPECTATION_FAILED.value,
84
+ metadata={"error": error.__str__()},
85
+ )
@@ -0,0 +1,5 @@
1
+ SHAREPOINT_BASE_URL = "/sites/uc365_CentrosyEstacionesRegionalesUC/Shared%20Documents/General/Test_Raspberry"
2
+ DESTINATION_FOLDERS = {
3
+ "CAM1": "Camaras_Trampa",
4
+ "RECORDS1": "Grabaciones",
5
+ }
@@ -1,22 +1,6 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import Dict
3
-
4
-
5
- @dataclass
6
- class FtpClientConfig:
7
- ftp_host: str
8
- ftp_port: int
9
- ftp_user: str
10
- ftp_password: str
11
-
12
-
13
- @dataclass
14
- class SharepointConfig:
15
- sharepoint_client_id: str
16
- sharepoint_client_secret: str
17
- sharepoint_tenant_id: str
18
- sharepoint_tenant_name: str
19
- sharepoint_site_name: str
3
+ from saviialib.general_types.api.epii_api_types import FtpClientConfig, SharepointConfig
20
4
 
21
5
 
22
6
  @dataclass
@@ -0,0 +1,8 @@
1
+ from dataclasses import dataclass
2
+ from saviialib.general_types.api.epii_api_types import SharepointConfig
3
+
4
+
5
+ @dataclass
6
+ class UploadBackupToSharepointUseCaseInput:
7
+ sharepoint_config: SharepointConfig
8
+ local_backup_source_path: str
@@ -1,7 +1,5 @@
1
- from dotenv import load_dotenv
2
-
3
- import saviialib.services.epii.constants.update_thies_data_constants as c
4
- from saviialib.general_types.error_types.api.update_thies_data_error_types import (
1
+ import saviialib.services.epii.use_cases.constants.update_thies_data_constants as c
2
+ from saviialib.general_types.error_types.api.epii_api_error_types import (
5
3
  SharePointFetchingError,
6
4
  SharePointUploadError,
7
5
  ThiesConnectionError,
@@ -33,8 +31,6 @@ from saviialib.services.epii.utils import (
33
31
  parse_execute_response,
34
32
  )
35
33
 
36
- load_dotenv()
37
-
38
34
 
39
35
  class UpdateThiesDataUseCase:
40
36
  def __init__(self, input: UpdateThiesDataUseCaseInput):
@@ -122,7 +118,7 @@ class UpdateThiesDataUseCase:
122
118
  self, files: dict
123
119
  ) -> dict[str, list[str]]:
124
120
  """Upload files to SharePoint and categorize the results."""
125
- upload_results = {"failed_files": [], "overwritten_files": [], "new_files": []}
121
+ upload_results = {"failed_files": [], "new_files": []}
126
122
 
127
123
  async with self.sharepoint_client:
128
124
  for file, file_content in files.items():
@@ -133,12 +129,8 @@ class UpdateThiesDataUseCase:
133
129
  file_content=file_content,
134
130
  file_name=file_name,
135
131
  )
136
- response = await self.sharepoint_client.upload_file(args)
137
-
138
- if response.get("Exists", False):
139
- upload_results["overwritten_files"].append(file)
140
- else:
141
- upload_results["new_files"].append(file)
132
+ await self.sharepoint_client.upload_file(args)
133
+ upload_results["new_files"].append(file)
142
134
 
143
135
  except ConnectionError as error:
144
136
  upload_results["failed_files"].append(
@@ -0,0 +1,201 @@
1
+ import asyncio
2
+ import os
3
+ from time import time
4
+ import saviialib.services.epii.use_cases.constants.upload_backup_to_sharepoint_constants as c
5
+ from saviialib.general_types.error_types.api.epii_api_error_types import (
6
+ BackupEmptyError,
7
+ BackupSourcePathError,
8
+ BackupUploadError,
9
+ )
10
+ from saviialib.general_types.error_types.common import (
11
+ SharepointClientError,
12
+ )
13
+ from saviialib.libs.files_client import FilesClient, FilesClientInitArgs, ReadArgs
14
+ from saviialib.libs.sharepoint_client import (
15
+ SharepointClient,
16
+ SharepointClientInitArgs,
17
+ SpUploadFileArgs,
18
+ )
19
+ from saviialib.services.epii.utils.upload_backup_to_sharepoint_utils import (
20
+ calculate_percentage_uploaded,
21
+ count_files_in_directory,
22
+ directory_exists,
23
+ extract_error_message,
24
+ parse_execute_response,
25
+ show_upload_result,
26
+ )
27
+
28
+ from .types.upload_backup_to_sharepoint_types import (
29
+ UploadBackupToSharepointUseCaseInput,
30
+ )
31
+
32
+
33
+ class UploadBackupToSharepointUsecase:
34
+ def __init__(self, input: UploadBackupToSharepointUseCaseInput):
35
+ self.sharepoint_config = input.sharepoint_config
36
+ self.local_backup_source_path = input.local_backup_source_path
37
+ self.grouped_files_by_folder = self._extract_filesnames_by_folder()
38
+ 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
+ self.log_history = []
43
+
44
+ def _initialize_files_client(self):
45
+ return FilesClient(FilesClientInitArgs(client_name="aiofiles_client"))
46
+
47
+ def _extract_filesnames_by_folder(self) -> dict[str, list[str]]:
48
+ """Groups files by their parent folder."""
49
+ if not os.path.exists(self.local_backup_source_path):
50
+ return {}
51
+ return {
52
+ folder_name: [
53
+ file_name
54
+ for file_name in os.listdir(
55
+ os.path.join(self.local_backup_source_path, folder_name)
56
+ )
57
+ ]
58
+ for folder_name in os.listdir(self.local_backup_source_path)
59
+ }
60
+
61
+ def _save_log_history(self) -> None:
62
+ text_file = "\n".join(self.log_history)
63
+ log_history_filepath = "BACKUP_LOG_HISTORY.log"
64
+ with open(log_history_filepath, "w") as file:
65
+ file.write(text_file)
66
+
67
+ async def export_file_to_sharepoint(
68
+ self, folder_name: str, file_name: str, file_content: bytes
69
+ ) -> tuple[bool, str]:
70
+ """Uploads a file to the specified folder in SharePoint."""
71
+ uploaded = None
72
+ error_message = ""
73
+
74
+ try:
75
+ sharepoint_client = SharepointClient(
76
+ SharepointClientInitArgs(
77
+ self.sharepoint_config, client_name="sharepoint_rest_api"
78
+ )
79
+ )
80
+ except ConnectionError as error:
81
+ raise SharepointClientError(error)
82
+
83
+ async with sharepoint_client:
84
+ try:
85
+ destination_folder = c.DESTINATION_FOLDERS.get(folder_name, folder_name)
86
+ folder_url = f"{c.SHAREPOINT_BASE_URL}/{destination_folder}"
87
+ args = SpUploadFileArgs(
88
+ folder_relative_url=folder_url,
89
+ file_content=file_content,
90
+ file_name=file_name,
91
+ )
92
+ await sharepoint_client.upload_file(args)
93
+ uploaded = True
94
+ except ConnectionError as error:
95
+ error_message = str(error)
96
+ uploaded = False
97
+
98
+ return uploaded, error_message
99
+
100
+ async def upload_and_log_progress_task(self, folder_name, file_name) -> dict:
101
+ """Task for uploads a file and logs progress."""
102
+ uploading_message = (
103
+ f"[BACKUP] Uploading file '{file_name}' from '{folder_name}' "
104
+ )
105
+ self.log_history.append(uploading_message)
106
+ print(uploading_message)
107
+ file_path = os.path.join(self.local_backup_source_path, folder_name, file_name)
108
+ file_content = await self.files_client.read(ReadArgs(file_path, mode="rb"))
109
+ uploaded, error_message = await self.export_file_to_sharepoint(
110
+ folder_name, file_name, file_content
111
+ )
112
+ result_message = show_upload_result(uploaded, file_name)
113
+ print(result_message)
114
+ self.log_history.append(result_message)
115
+ return {
116
+ "parent_folder": folder_name,
117
+ "file_name": file_name,
118
+ "uploaded": uploaded,
119
+ "error_message": error_message,
120
+ }
121
+
122
+ async def retry_upload_failed_files(self, results) -> None:
123
+ failed_files = [item for item in results if not item["uploaded"]]
124
+ tasks = []
125
+ retry_message = (
126
+ f"[BACKUP] Retrying upload for {len(failed_files)} failed files... 🚨"
127
+ )
128
+ self.log_history.append(retry_message)
129
+ print(retry_message)
130
+ for file in failed_files:
131
+ tasks.append(
132
+ self.upload_and_log_progress_task(
133
+ file["parent_folder"], file["file_name"]
134
+ )
135
+ )
136
+ results = await asyncio.gather(*tasks, return_exceptions=True)
137
+ success = calculate_percentage_uploaded(results, self.total_files)
138
+ if success < 100.0:
139
+ raise BackupUploadError(reason=extract_error_message(results, success))
140
+ else:
141
+ successful_upload_retry = (
142
+ "[BACKUP] All files uploaded successfully after retry."
143
+ )
144
+ self.log_history.append(successful_upload_retry)
145
+ print(successful_upload_retry)
146
+ self._save_log_history()
147
+ return parse_execute_response(results)
148
+
149
+ async def execute(self):
150
+ """Exports all files from the local backup folder to SharePoint cloud."""
151
+ tasks = []
152
+ start_time = time()
153
+ if not directory_exists(self.local_backup_source_path):
154
+ raise BackupSourcePathError(
155
+ reason=f"'{self.local_backup_source_path}' doesn't exist."
156
+ )
157
+ if self.total_files == 0:
158
+ no_files_message = (
159
+ f"[BACKUP] {self.local_backup_source_path} has no files ⚠️"
160
+ )
161
+ self.log_history.append(no_files_message)
162
+ print(no_files_message)
163
+ raise BackupEmptyError
164
+ # Create task for each file stored in the the local backup folder.
165
+ for folder_name in self.grouped_files_by_folder:
166
+ if (
167
+ count_files_in_directory(self.local_backup_source_path, folder_name)
168
+ == 0
169
+ ):
170
+ empty_folder_message = f"[BACKUP] The folder '{folder_name}' is empty ⚠️"
171
+ print(empty_folder_message)
172
+ self.log_history.append(empty_folder_message)
173
+ continue
174
+ extracting_files_message = (
175
+ "[BACKUP]" + f" Extracting files from '{folder_name} ".center(15, "*")
176
+ )
177
+ self.log_history.append(extracting_files_message)
178
+ print(extracting_files_message)
179
+ for file_name in self.grouped_files_by_folder[folder_name]:
180
+ tasks.append(self.upload_and_log_progress_task(folder_name, file_name))
181
+
182
+ # Execution of multiple asynchronous tasks for files migration.
183
+ results = await asyncio.gather(*tasks, return_exceptions=True)
184
+ success = calculate_percentage_uploaded(results, self.total_files)
185
+ if success < 100.0:
186
+ await self.retry_upload_failed_files(results)
187
+ else:
188
+ end_time = time()
189
+ backup_time = end_time - start_time
190
+ successful_backup_message = (
191
+ f"[BACKUP] Migration time: {backup_time:.2f} seconds ✨"
192
+ )
193
+ self.log_history.append(successful_backup_message)
194
+
195
+ finished_backup_message = (
196
+ "[BACKUP] All the files were uploaded successfully 🎉"
197
+ )
198
+ self.log_history.append(finished_backup_message)
199
+
200
+ self._save_log_history()
201
+ return parse_execute_response(results)
@@ -0,0 +1,92 @@
1
+ import re
2
+ from typing import List, Dict, Optional
3
+ import os
4
+
5
+
6
+ def extract_error_information(error: str) -> Optional[Dict[str, str]]:
7
+ match = re.search(r"(\d+), message='([^']*)', url=\"([^\"]*)\"", error)
8
+ if match:
9
+ return {
10
+ "status_code": match.group(1),
11
+ "message": match.group(2),
12
+ "url": match.group(3),
13
+ }
14
+ return None
15
+
16
+
17
+ def explain_status_code(status_code: int) -> str:
18
+ explanations = {
19
+ 404: "Probably an error with file or folder source path.",
20
+ 403: "Permission denied when accessing the source path.",
21
+ 500: "Internal server error occurred during upload.",
22
+ }
23
+ return explanations.get(status_code, "Unknown error occurred.")
24
+
25
+
26
+ def extract_error_message(results: List[Dict], success: float) -> str:
27
+ print(
28
+ "[BACKUP] Not all files uploaded ⚠️\n"
29
+ f"[BACKUP] Files failed to upload: {(1 - success):.2%}"
30
+ )
31
+
32
+ failed_files = [item for item in results if not item.get("uploaded")]
33
+
34
+ error_data = []
35
+ for item in failed_files:
36
+ error_info = extract_error_information(item.get("error_message", ""))
37
+ if error_info:
38
+ error_data.append(
39
+ {
40
+ "file_name": item["file_name"],
41
+ "status_code": error_info["status_code"],
42
+ "message": error_info["message"],
43
+ "url": error_info["url"],
44
+ }
45
+ )
46
+
47
+ # Group errors by code.
48
+ grouped_errors: Dict[str, List[Dict]] = {}
49
+ for error in error_data:
50
+ code = error["status_code"]
51
+ grouped_errors.setdefault(code, []).append(error)
52
+
53
+ # Summary
54
+ for code, items in grouped_errors.items():
55
+ print(f"[BACKUP] Status code {code} - {explain_status_code(int(code))}")
56
+ for item in items:
57
+ print(
58
+ f"[BACKUP] File {item['file_name']}, url: {item['url']}, message: {item['message']}"
59
+ )
60
+
61
+ failed_file_names = [item["file_name"] for item in failed_files]
62
+ return f"Failed files: {', '.join(failed_file_names)}."
63
+
64
+
65
+ def parse_execute_response(results: List[Dict]) -> Dict[str, List[str]]:
66
+ return {
67
+ "new_files": len(
68
+ [item["file_name"] for item in results if item.get("uploaded")]
69
+ ),
70
+ }
71
+
72
+
73
+ def show_upload_result(uploaded: bool, file_name: str) -> str:
74
+ status = "✅" if uploaded else "❌"
75
+ message = "was uploaded successfully" if uploaded else "failed to upload"
76
+ result = f"[BACKUP] File {file_name} {message} {status}"
77
+ return result
78
+
79
+
80
+ def calculate_percentage_uploaded(results: List[Dict], total_files: int) -> float:
81
+ uploaded_count = sum(
82
+ 1 for result in results if isinstance(result, dict) and result.get("uploaded")
83
+ )
84
+ return (uploaded_count / total_files) * 100 if total_files > 0 else 0
85
+
86
+
87
+ def directory_exists(path: str) -> bool:
88
+ return os.path.exists(path)
89
+
90
+
91
+ def count_files_in_directory(path: str, folder_name: str) -> int:
92
+ return len(os.listdir(os.path.join(path, folder_name)))
@@ -1,3 +0,0 @@
1
- from .update_thies_data_types import EpiiUpdateThiesConfig
2
-
3
- __all__ = ["EpiiUpdateThiesConfig"]
@@ -1,30 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
-
4
- @dataclass
5
- class EpiiUpdateThiesConfig:
6
- """
7
- Configuration for Epii API.
8
-
9
- Attributes:
10
- ftp_port (int): Port number of the FTP server.
11
- ftp_host (str): Hostname or IP address of the FTP server.
12
- ftp_user (str): Username for the FTP server.
13
- ftp_password (str): Password for the FTP server.
14
- sharepoint_client_id (str): Client ID for SharePoint authentication.
15
- sharepoint_client_secret (str): Client secret for SharePoint authentication.
16
- sharepoint_tenant_id (str): Tenant ID for SharePoint authentication.
17
- sharepoint_tenant_name (str): Tenant name for SharePoint.
18
- sharepoint_site_name (str): Site name in SharePoint.
19
- logger (Logger): Logger object for logging during synchronisation of files from THIES Data Logger
20
- """
21
-
22
- ftp_port: int
23
- ftp_host: str
24
- ftp_user: str
25
- ftp_password: str
26
- sharepoint_client_id: str
27
- sharepoint_client_secret: str
28
- sharepoint_tenant_id: str
29
- sharepoint_tenant_name: str
30
- sharepoint_site_name: str
@@ -1,26 +0,0 @@
1
- from typing import Any, Dict
2
-
3
- from .controllers.types.update_thies_data_types import UpdateThiesDataControllerInput
4
- from .controllers.update_thies_data import UpdateThiesDataController
5
- from saviialib.general_types.api.update_thies_data_types import (
6
- EpiiUpdateThiesConfig,
7
- )
8
-
9
-
10
- class EpiiAPI:
11
- """
12
- EpiiAPI is a service class that provides methods to interact with Patagonia Center system.
13
- """
14
-
15
- async def update_thies_data(self, config: EpiiUpdateThiesConfig) -> Dict[str, Any]:
16
- """
17
- This method establishes a connection to an FTP server using the provided
18
- credentials and updates data related to THIES Data Logger.
19
- Args:
20
- config (EpiiUpdateThiesConfig): configuration class for FTP Server and Microsoft SharePoint credentials.
21
- Returns:
22
- response (dict): A dictionary representation of the API response.
23
- """
24
- controller = UpdateThiesDataController(UpdateThiesDataControllerInput(config))
25
- response = await controller.execute()
26
- return response.__dict__
@@ -1,3 +0,0 @@
1
- from .update_thies_data import UpdateThiesDataController
2
-
3
- __all__ = ["UpdateThiesDataController"]
File without changes