rcer-iot-client-pkg 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. rcer_iot_client_pkg/__init__.py +8 -0
  2. rcer_iot_client_pkg/general_types/__init__.py +0 -0
  3. rcer_iot_client_pkg/general_types/api/__init__.py +0 -0
  4. rcer_iot_client_pkg/general_types/api/update_thies_data_types.py +0 -0
  5. rcer_iot_client_pkg/general_types/error_types/__init__.py +0 -0
  6. rcer_iot_client_pkg/general_types/error_types/api/__init__.py +0 -0
  7. rcer_iot_client_pkg/general_types/error_types/api/update_thies_data_error_types.py +19 -0
  8. rcer_iot_client_pkg/general_types/error_types/common/__init__.py +7 -0
  9. rcer_iot_client_pkg/general_types/error_types/common/common_types.py +13 -0
  10. rcer_iot_client_pkg/libs/async_http_client/__init__.py +10 -0
  11. rcer_iot_client_pkg/libs/async_http_client/async_http_client.py +34 -0
  12. rcer_iot_client_pkg/libs/async_http_client/async_http_client_contract.py +29 -0
  13. rcer_iot_client_pkg/libs/async_http_client/clients/__init__.py +0 -0
  14. rcer_iot_client_pkg/libs/async_http_client/clients/aiohttp_client.py +50 -0
  15. rcer_iot_client_pkg/libs/async_http_client/types/__init__.py +3 -0
  16. rcer_iot_client_pkg/libs/async_http_client/types/async_http_client_types.py +17 -0
  17. rcer_iot_client_pkg/libs/ftp_client/__init__.py +4 -0
  18. rcer_iot_client_pkg/libs/ftp_client/clients/__init__.py +0 -0
  19. rcer_iot_client_pkg/libs/ftp_client/clients/aioftp_client.py +34 -0
  20. rcer_iot_client_pkg/libs/ftp_client/ftp_client.py +22 -0
  21. rcer_iot_client_pkg/libs/ftp_client/ftp_client_contract.py +13 -0
  22. rcer_iot_client_pkg/libs/ftp_client/types/__init__.py +3 -0
  23. rcer_iot_client_pkg/libs/ftp_client/types/ftp_client_types.py +20 -0
  24. rcer_iot_client_pkg/libs/zero_dependency/utils/datetime_utils.py +25 -0
  25. rcer_iot_client_pkg/services/epii/__init__.py +0 -0
  26. rcer_iot_client_pkg/services/epii/api.py +23 -0
  27. rcer_iot_client_pkg/services/epii/controllers/__init__.py +5 -0
  28. rcer_iot_client_pkg/services/epii/controllers/types/__init__.py +6 -0
  29. rcer_iot_client_pkg/services/epii/controllers/types/update_thies_data_types.py +17 -0
  30. rcer_iot_client_pkg/services/epii/controllers/update_thies_data.py +68 -0
  31. rcer_iot_client_pkg/services/epii/use_cases/constants.py +4 -0
  32. rcer_iot_client_pkg/services/epii/use_cases/types/__init__.py +3 -0
  33. rcer_iot_client_pkg/services/epii/use_cases/types/update_thies_data_types.py +17 -0
  34. rcer_iot_client_pkg/services/epii/use_cases/update_thies_data.py +135 -0
  35. rcer_iot_client_pkg/services/epii/utils/__init__.py +3 -0
  36. rcer_iot_client_pkg/services/epii/utils/update_thies_data_utils.py +18 -0
  37. rcer_iot_client_pkg-0.1.0.dist-info/LICENSE +22 -0
  38. rcer_iot_client_pkg-0.1.0.dist-info/METADATA +82 -0
  39. rcer_iot_client_pkg-0.1.0.dist-info/RECORD +40 -0
  40. rcer_iot_client_pkg-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,8 @@
1
+ # read version from installed package
2
+ from importlib.metadata import version
3
+
4
+ __version__ = version("rcer_iot_client_pkg")
5
+
6
+ from .services.epii.api import EpiiAPI
7
+
8
+ __all__ = ["EpiiAPI"]
File without changes
File without changes
@@ -0,0 +1,19 @@
1
+ class ThiesUploadEmptyError(Exception):
2
+ """Raised when no files are found to upload to the server."""
3
+
4
+ def __str__(self):
5
+ return "No files were found to upload."
6
+
7
+
8
+ class FetchCloudFileNamesError(Exception):
9
+ """Raised when there is an error fetching file names from the RCER cloud."""
10
+
11
+ def __str__(self):
12
+ return "An error occurred while retrieving file names from the RCER cloud"
13
+
14
+
15
+ class FetchThiesFileContentError(Exception):
16
+ """Raised when there is an error fetching the content of a Thies file."""
17
+
18
+ def __str__(self):
19
+ return "An error occurred while retrieving the content of a Thies file"
@@ -0,0 +1,7 @@
1
+ from src.rcer_iot_client_pkg.general_types.error_types.common.common_types import (
2
+ EmptyDataError,
3
+ FtpClientError,
4
+ HttpClientError,
5
+ )
6
+
7
+ __all__ = ["EmptyDataError", "HttpClientError", "FtpClientError"]
@@ -0,0 +1,13 @@
1
+ class EmptyDataError(Exception):
2
+ def __str__(self):
3
+ return "The data provided is empty."
4
+
5
+
6
+ class HttpClientError(Exception):
7
+ def __str__(self):
8
+ return "Http Client initialization fails."
9
+
10
+
11
+ class FtpClientError(Exception):
12
+ def __str__(self):
13
+ return "Ftp Client initialization fails."
@@ -0,0 +1,10 @@
1
+ """Export defined type classes."""
2
+
3
+ from .async_http_client import AsyncHTTPClient
4
+ from .types.async_http_client_types import (
5
+ AsyncHttpClientInitArgs,
6
+ GetArgs,
7
+ UploadFileArgs,
8
+ )
9
+
10
+ __all__ = ["AsyncHTTPClient", "AsyncHttpClientInitArgs", "GetArgs", "UploadFileArgs"]
@@ -0,0 +1,34 @@
1
+ from typing import Any
2
+
3
+ from .async_http_client_contract import AsyncHTTPClientContract
4
+ from .clients.aiohttp_client import AioHttpClient
5
+ from .types.async_http_client_types import (
6
+ AsyncHttpClientInitArgs,
7
+ GetArgs,
8
+ UploadFileArgs,
9
+ )
10
+
11
+
12
+ class AsyncHTTPClient(AsyncHTTPClientContract):
13
+ CLIENTS = {"aiohttp_client"}
14
+
15
+ def __init__(self, args: AsyncHttpClientInitArgs) -> None:
16
+ if args.client_name not in AsyncHTTPClient.CLIENTS:
17
+ msg = f"Unsupported client '{args.client_name}'"
18
+ raise KeyError(msg)
19
+ self.client_name = args.client_name
20
+
21
+ if args.client_name == "aiohttp_client":
22
+ self.client_obj = AioHttpClient(args)
23
+
24
+ async def __aenter__(self):
25
+ return await self.client_obj.__aenter__()
26
+
27
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
28
+ await self.client_obj.__aexit__(exc_type, exc_val, exc_tb)
29
+
30
+ async def get(self, args: GetArgs) -> dict[str, Any]:
31
+ return await self.client_obj.get(args)
32
+
33
+ async def upload_file(self, args: UploadFileArgs) -> dict[str, Any]:
34
+ return await self.client_obj.upload_file(args)
@@ -0,0 +1,29 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ from .types.async_http_client_types import GetArgs, UploadFileArgs
5
+
6
+
7
+ class AsyncHTTPClientContract(ABC):
8
+ """
9
+ A contract for asynchronous HTTP client implementations.
10
+
11
+ This abstract base class defines the required methods for performing
12
+ HTTP GET requests and uploading files asynchronously.
13
+
14
+ Methods.
15
+ -------
16
+ get(args: GetArgs) -> dict[str, Any]
17
+ Perform an HTTP GET request with the specified arguments.
18
+
19
+ upload_file(args: UploadFileArgs) -> dict[str, Any]
20
+ Upload a file using the specified arguments.
21
+ """
22
+
23
+ @abstractmethod
24
+ async def get(self, args: GetArgs) -> dict[str, Any]:
25
+ pass
26
+
27
+ @abstractmethod
28
+ async def upload_file(self, args: UploadFileArgs) -> dict[str, Any]:
29
+ pass
@@ -0,0 +1,50 @@
1
+ from typing import Any
2
+
3
+ from aiohttp import ClientError, ClientSession
4
+
5
+ from src.rcer_iot_client_pkg.libs.async_http_client.async_http_client_contract import (
6
+ AsyncHTTPClientContract,
7
+ )
8
+ from src.rcer_iot_client_pkg.libs.async_http_client.types.async_http_client_types import (
9
+ AsyncHttpClientInitArgs,
10
+ GetArgs,
11
+ UploadFileArgs,
12
+ )
13
+
14
+
15
+ class AioHttpClient(AsyncHTTPClientContract):
16
+ def __init__(self, args: AsyncHttpClientInitArgs) -> None:
17
+ self.access_token = args.access_token
18
+ self.base_url = args.base_url
19
+ self.headers = self._build_headers()
20
+ self.session: ClientSession | None = None
21
+
22
+ def _build_headers(self) -> dict:
23
+ return {"Authorization": f"Bearer {self.access_token}"}
24
+
25
+ async def __aenter__(self) -> "AioHttpClient":
26
+ self.session = ClientSession(headers=self.headers, base_url=self.base_url)
27
+ return self
28
+
29
+ async def __aexit__(
30
+ self, _exc_type: type[BaseException], _exc_val: BaseException, _exc_tb: Any
31
+ ) -> None:
32
+ await self.session.close()
33
+
34
+ async def get(self, args: GetArgs) -> dict[str, Any]:
35
+ try:
36
+ endpoint, params = args.endpoint.lstrip("/"), args.params
37
+ response = await self.session.get(endpoint, params=params)
38
+ response.raise_for_status()
39
+ return await response.json()
40
+ except ClientError as error:
41
+ raise ConnectionError(error) from error
42
+
43
+ async def upload_file(self, args: UploadFileArgs) -> dict[str, Any]:
44
+ try:
45
+ endpoint, file_bytes = args.endpoint.lstrip("/"), args.file_bytes
46
+ response = await self.session.put(endpoint, data=file_bytes)
47
+ response.raise_for_status()
48
+ return await response.json()
49
+ except ClientError as error:
50
+ raise ConnectionError(error) from error
@@ -0,0 +1,3 @@
1
+ from .async_http_client_types import AsyncHttpClientInitArgs, GetArgs, UploadFileArgs
2
+
3
+ __all__ = ["AsyncHttpClientInitArgs", "GetArgs", "UploadFileArgs"]
@@ -0,0 +1,17 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class AsyncHttpClientInitArgs(BaseModel):
5
+ access_token: str
6
+ base_url: str
7
+ client_name: str = "aiohttp_client"
8
+
9
+
10
+ class GetArgs(BaseModel):
11
+ endpoint: str
12
+ params: dict | None = Field(default=None)
13
+
14
+
15
+ class UploadFileArgs(BaseModel):
16
+ endpoint: str
17
+ file_bytes: bytes
@@ -0,0 +1,4 @@
1
+ from .ftp_client import FTPClient
2
+ from .types import FtpClientInitArgs, ListFilesArgs, ReadFileArgs
3
+
4
+ __all__ = ["FTPClient", "FtpClientInitArgs", "ListFilesArgs", "ReadFileArgs"]
@@ -0,0 +1,34 @@
1
+ from aioftp import Client
2
+
3
+ from src.rcer_iot_client_pkg.libs.ftp_client.ftp_client_contract import (
4
+ FTPClientContract,
5
+ )
6
+ from src.rcer_iot_client_pkg.libs.ftp_client.types.ftp_client_types import (
7
+ FtpClientInitArgs,
8
+ ListFilesArgs,
9
+ ReadFileArgs,
10
+ )
11
+
12
+
13
+ class AioFTPClient(FTPClientContract):
14
+ def __init__(self, args: FtpClientInitArgs) -> None:
15
+ self.host = args.host
16
+ self.port = args.port
17
+ self.password = args.password
18
+ self.user = args.user
19
+ self.client = Client()
20
+
21
+ async def _async_start(self) -> None:
22
+ await self.client.connect(host=self.host, port=self.port)
23
+ await self.client.login(user=self.user, password=self.password)
24
+
25
+ async def list_files(self, args: ListFilesArgs) -> list[str]:
26
+ await self._async_start()
27
+ return [
28
+ path.name async for path, _ in self.client.list(args.path, recursive=False)
29
+ ]
30
+
31
+ async def read_file(self, args: ReadFileArgs) -> bytes:
32
+ await self._async_start()
33
+ async with self.client.download_stream(args.file_path) as stream:
34
+ return await stream.read()
@@ -0,0 +1,22 @@
1
+ from .clients.aioftp_client import AioFTPClient
2
+ from .ftp_client_contract import FTPClientContract
3
+ from .types.ftp_client_types import FtpClientInitArgs, ListFilesArgs, ReadFileArgs
4
+
5
+
6
+ class FTPClient(FTPClientContract):
7
+ CLIENTS = {"aioftp_client"}
8
+
9
+ def __init__(self, args: FtpClientInitArgs) -> None:
10
+ if args.client_name not in FTPClient.CLIENTS:
11
+ msg = f"Unsupported client {args.client_name}"
12
+ raise KeyError(msg)
13
+
14
+ if args.client_name == "aioftp_client":
15
+ self.client_obj = AioFTPClient(args)
16
+ self.client_name = args.client_name
17
+
18
+ def list_files(self, args: ListFilesArgs) -> list[str]:
19
+ return self.client_obj.list_files(args)
20
+
21
+ def read_file(self, args: ReadFileArgs) -> bytes:
22
+ return self.client_obj.read_file(args)
@@ -0,0 +1,13 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from .types.ftp_client_types import ListFilesArgs, ReadFileArgs
4
+
5
+
6
+ class FTPClientContract(ABC):
7
+ @abstractmethod
8
+ def list_files(self, args: ListFilesArgs) -> list[str]:
9
+ pass
10
+
11
+ @abstractmethod
12
+ def read_file(self, args: ReadFileArgs) -> bytes:
13
+ pass
@@ -0,0 +1,3 @@
1
+ from .ftp_client_types import FtpClientInitArgs, ListFilesArgs, ReadFileArgs
2
+
3
+ __all__ = ["FtpClientInitArgs", "ListFilesArgs", "ReadFileArgs"]
@@ -0,0 +1,20 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class FtpClientInitArgs:
6
+ host: str
7
+ user: str
8
+ password: str
9
+ client_name: str = "aioftp_client"
10
+ port: int = 21
11
+
12
+
13
+ @dataclass
14
+ class ListFilesArgs:
15
+ path: str
16
+
17
+
18
+ @dataclass
19
+ class ReadFileArgs:
20
+ file_path: str
@@ -0,0 +1,25 @@
1
+ from datetime import datetime
2
+ from zoneinfo import ZoneInfo
3
+
4
+
5
+ def today(timezone: str = "America/Santiago") -> str:
6
+ """
7
+ Return the current date.
8
+
9
+ :param timezone: A string representing the IANA timezone name.
10
+ Defaults to "America/Santiago".
11
+ :return datetime:
12
+ """
13
+ return datetime.now(tz=ZoneInfo(timezone))
14
+
15
+
16
+ def datetime_to_str(date: datetime, date_format: str = "%Y-%m-%dT%H:%M%:S") -> str:
17
+ """
18
+ Convert a datetime object to a string in the specified format.
19
+
20
+ :param date: The datetime object to convert.
21
+ :param date_format: The format to convert the datetime object to.
22
+ Defaults to "YYYYMMDD:HHMMSS".
23
+ :return: A string in the specified format.
24
+ """
25
+ return date.strftime(date_format)
File without changes
@@ -0,0 +1,23 @@
1
+ from typing import Dict
2
+
3
+ from src.rcer_iot_client_pkg.services.epii.controllers import UpdateThiesDataController
4
+ from src.rcer_iot_client_pkg.services.epii.controllers.types import UpdateThiesDataControllerInput
5
+
6
+ class EpiiAPI:
7
+ def update_thies_data(
8
+ self,
9
+ ftp_port: int,
10
+ ftp_host: str,
11
+ ftp_password: str,
12
+ ftp_user: str,
13
+ ) -> Dict[str, any]:
14
+ controller = UpdateThiesDataController(
15
+ UpdateThiesDataControllerInput(
16
+ ftp_port=ftp_port,
17
+ ftp_host=ftp_host,
18
+ ftp_password=ftp_password,
19
+ ftp_user=ftp_user,
20
+ )
21
+ )
22
+ response = controller.execute()
23
+ return response.__dict__
@@ -0,0 +1,5 @@
1
+ from src.rcer_iot_client_pkg.services.epii.controllers.update_thies_data import (
2
+ UpdateThiesDataController,
3
+ )
4
+
5
+ __all__ = ["UpdateThiesDataController"]
@@ -0,0 +1,6 @@
1
+ from src.rcer_iot_client_pkg.services.epii.controllers.types.update_thies_data_types import (
2
+ UpdateThiesDataControllerInput,
3
+ UpdateThiesDataControllerOutput,
4
+ )
5
+
6
+ __all__ = ["UpdateThiesDataControllerInput", "UpdateThiesDataControllerOutput"]
@@ -0,0 +1,17 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Dict
3
+
4
+
5
+ @dataclass
6
+ class UpdateThiesDataControllerInput:
7
+ ftp_host: str
8
+ ftp_port: str
9
+ ftp_user: str
10
+ ftp_password: str
11
+
12
+
13
+ @dataclass
14
+ class UpdateThiesDataControllerOutput:
15
+ message: str
16
+ status: int
17
+ metadata: Dict[str, str] = field(default_factory=dict)
@@ -0,0 +1,68 @@
1
+ from http import HTTPStatus
2
+
3
+ from src.rcer_iot_client_pkg.general_types.error_types.api.update_thies_data_error_types import (
4
+ FetchCloudFileNamesError,
5
+ ThiesUploadEmptyError,
6
+ )
7
+ from src.rcer_iot_client_pkg.general_types.error_types.common import (
8
+ FtpClientError,
9
+ HttpClientError,
10
+ )
11
+ from src.rcer_iot_client_pkg.services.epii.controllers.types.update_thies_data_types import (
12
+ UpdateThiesDataControllerInput,
13
+ UpdateThiesDataControllerOutput,
14
+ )
15
+ from src.rcer_iot_client_pkg.services.epii.use_cases.types import (
16
+ UpdateThiesDataUseCaseInput,
17
+ )
18
+ from src.rcer_iot_client_pkg.services.epii.use_cases.update_thies_data import (
19
+ UpdateThiesDataUseCase,
20
+ )
21
+
22
+
23
+ class UpdateThiesDataController:
24
+ def __init__(self, input: UpdateThiesDataControllerInput):
25
+ self.use_case = UpdateThiesDataUseCase(
26
+ UpdateThiesDataUseCaseInput(**input.__dict__)
27
+ )
28
+
29
+ async def execute(self) -> UpdateThiesDataControllerOutput:
30
+ try:
31
+ data = await self.use_case.execute()
32
+ return UpdateThiesDataControllerOutput(
33
+ message="THIES was synced successfully",
34
+ status=HTTPStatus.OK,
35
+ metadata={"data": data},
36
+ )
37
+ except (AttributeError, NameError, ValueError) as error:
38
+ return UpdateThiesDataControllerOutput(
39
+ message="An unexpected error occurred during use case initialization.",
40
+ status=HTTPStatus.BAD_REQUEST,
41
+ metadata={"error": error.__str__()},
42
+ )
43
+ except FtpClientError as error:
44
+ return UpdateThiesDataControllerOutput(
45
+ message="Ftp Client initialization fails.",
46
+ status=HTTPStatus.INTERNAL_SERVER_ERROR,
47
+ metadata={"error": error.__str__()},
48
+ )
49
+
50
+ except HttpClientError as error:
51
+ return UpdateThiesDataControllerOutput(
52
+ message="Http Client initialization fails.",
53
+ status=HTTPStatus.INTERNAL_SERVER_ERROR,
54
+ metadata={"error": error.__str__()},
55
+ )
56
+
57
+ except FetchCloudFileNamesError as error:
58
+ return UpdateThiesDataControllerOutput(
59
+ message="An error occurred while retrieving file names from the RCER cloud",
60
+ status=HTTPStatus.INTERNAL_SERVER_ERROR,
61
+ metadata={"error": error.__str__()},
62
+ )
63
+ except ThiesUploadEmptyError as error:
64
+ return UpdateThiesDataControllerOutput(
65
+ message="No files were found to upload.",
66
+ status=HTTPStatus.NO_CONTENT,
67
+ metadata={"error": error.__str__()},
68
+ )
@@ -0,0 +1,4 @@
1
+ PATH_AVG_FILES = "ftp/thies/BINFILES/ARCH_AV1"
2
+ PATH_EXT_FILES = "ftp/thies/BINFILES/ARCH_EX1"
3
+ DRIVE_ID = "b!Row14jaFrU-1q8qzrvj3OmPPTYWXizFEpJmI-wsfH5pXxA0qQwgQS50m2xvPCZem"
4
+ FILE_TYPES = ["AVG", "EXT"]
@@ -0,0 +1,3 @@
1
+ from .update_thies_data_types import UpdateThiesDataUseCaseInput
2
+
3
+ __all__ = ["UpdateThiesDataUseCaseInput"]
@@ -0,0 +1,17 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Dict
3
+
4
+
5
+ @dataclass
6
+ class UpdateThiesDataUseCaseInput:
7
+ ftp_host: str
8
+ ftp_port: str
9
+ ftp_user: str
10
+ ftp_password: str
11
+
12
+
13
+ @dataclass
14
+ class UpdateThiesDataUseCaseOutput:
15
+ message: str
16
+ status: int = 0
17
+ metadata: Dict[str, str] = field(default_factory=dict)
@@ -0,0 +1,135 @@
1
+ import os
2
+
3
+ from dotenv import load_dotenv
4
+
5
+ import src.rcer_iot_client_pkg.services.epii.use_cases.constants as c
6
+ from src.rcer_iot_client_pkg.general_types.error_types.api.update_thies_data_error_types import (
7
+ FetchCloudFileNamesError,
8
+ FetchThiesFileContentError,
9
+ ThiesUploadEmptyError,
10
+ )
11
+ from src.rcer_iot_client_pkg.general_types.error_types.common import (
12
+ EmptyDataError,
13
+ FtpClientError,
14
+ HttpClientError,
15
+ )
16
+ from src.rcer_iot_client_pkg.libs.async_http_client import (
17
+ AsyncHTTPClient,
18
+ AsyncHttpClientInitArgs,
19
+ GetArgs,
20
+ )
21
+ from src.rcer_iot_client_pkg.libs.ftp_client import (
22
+ FTPClient,
23
+ FtpClientInitArgs,
24
+ ListFilesArgs,
25
+ ReadFileArgs,
26
+ )
27
+ from src.rcer_iot_client_pkg.services.epii.use_cases.types import (
28
+ UpdateThiesDataUseCaseInput,
29
+ )
30
+ from src.rcer_iot_client_pkg.services.epii.utils import (
31
+ generate_file_content,
32
+ )
33
+
34
+ load_dotenv()
35
+
36
+
37
+ class UpdateThiesDataUseCase:
38
+ def __init__(self, input: UpdateThiesDataUseCaseInput):
39
+ self.ftp_port = input.ftp_port
40
+ self.ftp_host = input.ftp_host
41
+ self.ftp_password = input.ftp_password
42
+ self.ftp_user = input.ftp_user
43
+ self.sharepoint_client = self._initialize_sharepoint_client()
44
+ self.thies_ftp_client = self._initialize_thies_ftp_client()
45
+ self.uploading = set()
46
+
47
+ def _initialize_sharepoint_client(self) -> AsyncHTTPClient:
48
+ """Initialize the HTTP client."""
49
+ try:
50
+ return AsyncHTTPClient(
51
+ AsyncHttpClientInitArgs(
52
+ client_name="aiohttp_client",
53
+ access_token="temporal-token",
54
+ base_url="https://graph.microsoft.com/v1.0/",
55
+ )
56
+ )
57
+ except Exception as error:
58
+ raise HttpClientError(error)
59
+
60
+ def _initialize_thies_ftp_client(self) -> FTPClient:
61
+ """Initialize the FTP client."""
62
+ try:
63
+ return FTPClient(
64
+ FtpClientInitArgs(
65
+ client_name="aioftp_client",
66
+ host=self.ftp_host,
67
+ user=self.ftp_user,
68
+ password=self.ftp_password,
69
+ port=self.ftp_port,
70
+ )
71
+ )
72
+ except Exception as error:
73
+ raise FtpClientError(error)
74
+
75
+ async def fetch_cloud_file_names(self, folder_name: str) -> set[str]:
76
+ """Fetch file names from the RCER cloud."""
77
+ try:
78
+ cloud_files = set()
79
+ async with self.sharepoint_client:
80
+ for file_type in c.FILE_TYPES:
81
+ destination_path = f"Onedrive_UC/noveno-semestre/IPRE-RCER/{folder_name}/{file_type}"
82
+ endpoint = f"drives/{c.DRIVE_ID}/root:/{destination_path}:/children"
83
+ response = await self.sharepoint_client.get(
84
+ GetArgs(endpoint=endpoint)
85
+ )
86
+ cloud_files.update(
87
+ {f"{file_type}_{item['name']}" for item in response["value"]}
88
+ )
89
+ return cloud_files
90
+ except ConnectionError as error:
91
+ raise FetchCloudFileNamesError(error)
92
+
93
+ async def fetch_thies_file_names(self) -> set[str]:
94
+ """Fetch file names from the THIES FTP server."""
95
+ try:
96
+ avg_files = await self.thies_ftp_client.list_files(
97
+ ListFilesArgs(path=c.PATH_AVG_FILES)
98
+ )
99
+ ext_files = await self.thies_ftp_client.list_files(
100
+ ListFilesArgs(path=c.PATH_EXT_FILES)
101
+ )
102
+ return {f"AVG_{name}" for name in avg_files} | {
103
+ f"EXT_{name}" for name in ext_files
104
+ }
105
+ except ConnectionError:
106
+ raise ThiesUploadEmptyError
107
+
108
+ async def fetch_thies_file_content(self) -> dict[str, bytes]:
109
+ """Fetch the content of files from the THIES FTP server."""
110
+ content_files = {}
111
+ for file in self.uploading:
112
+ try:
113
+ origin, filename = file.split("_", 1)
114
+ file_path = (
115
+ f"{c.PATH_AVG_FILES}/{filename}"
116
+ if origin == "AVG"
117
+ else f"{c.PATH_EXT_FILES}/{filename}"
118
+ )
119
+ content = await self.thies_ftp_client.read_file(ReadFileArgs(file_path))
120
+ content_files[filename] = content
121
+ except ConnectionError as error:
122
+ raise FetchThiesFileContentError(error)
123
+ return content_files
124
+
125
+ async def execute(self) -> dict:
126
+ """Synchronize data from the THIES Center to the cloud."""
127
+ thies_files = await self.fetch_thies_file_names()
128
+ cloud_files = await self.fetch_cloud_file_names(folder_name="thies")
129
+ self.uploading = thies_files - cloud_files
130
+ if not self.uploading:
131
+ raise EmptyDataError
132
+
133
+ thies_file_contents = await self.fetch_thies_file_content()
134
+ data = generate_file_content(thies_file_contents)
135
+ return data
@@ -0,0 +1,3 @@
1
+ from src.rcer_iot_client_pkg.services.epii.utils.update_thies_data_utils import generate_file_content
2
+
3
+ __all__ = ["generate_file_content"]
@@ -0,0 +1,18 @@
1
+ from typing import Any
2
+
3
+ from src.rcer_iot_client_pkg.libs.zero_dependency.utils.datetime_utils import (
4
+ datetime_to_str,
5
+ today,
6
+ )
7
+
8
+
9
+ def generate_file_content(
10
+ file_contents: dict[str, Any],
11
+ ) -> dict[str, dict[str, int | str]]:
12
+ return {
13
+ filename: {
14
+ "size": len(data),
15
+ "date": datetime_to_str(today()),
16
+ }
17
+ for filename, data in file_contents.items()
18
+ }
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025, pedropablozavalat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.3
2
+ Name: rcer_iot_client_pkg
3
+ Version: 0.1.0
4
+ Summary: A client library for IoT projects in the RCER initiative
5
+ License: MIT
6
+ Author: pedropablozavalat
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: aioftp (==0.25.1)
15
+ Requires-Dist: aiohttp (==3.11.16)
16
+ Requires-Dist: build
17
+ Requires-Dist: dotenv (==0.9.9)
18
+ Requires-Dist: pydantic (==2.11.3)
19
+ Requires-Dist: pytest-cov (==6.1.1)
20
+ Description-Content-Type: text/markdown
21
+
22
+ # rcer_iot_client_pkg
23
+
24
+ RCER IoT Client Library
25
+
26
+ ## Installation
27
+
28
+ To install the package, run:
29
+
30
+ ```bash
31
+ pip install rcer_iot_client_pkg
32
+ ```
33
+
34
+ ## Development
35
+
36
+ This project includes a `Makefile` to simplify common tasks. Below are the available commands:
37
+
38
+ ### Install Basic Dependencies
39
+ To install the basic dependencies required for the project, run the following command:
40
+
41
+ ```bash
42
+ make install-deps
43
+ ```
44
+
45
+ This will ensure that all necessary libraries and tools are installed for the project to function properly.
46
+
47
+ ### Install Development Requirements
48
+ For setting up a development environment with additional tools and libraries, execute:
49
+
50
+ ```bash
51
+ make dev
52
+ ```
53
+
54
+ This command installs all the dependencies needed for development, including testing and linting tools.
55
+
56
+ ### Run Tests
57
+ To verify that the code is functioning as expected, you can run the test suite using:
58
+
59
+ ```bash
60
+ make test
61
+ ```
62
+
63
+ This will execute all the tests in the project and provide a summary of the results.
64
+
65
+ ### Lint the Code
66
+ To ensure that the code adheres to the project's style guidelines and is free of common errors, run:
67
+
68
+ ```bash
69
+ make lint
70
+ ```
71
+
72
+ This command checks the codebase for linting issues and outputs any problems that need to be addressed.
73
+
74
+ ## Contributing
75
+ If you're interested in contributing to this project, please follow the contributing guidelines. Contributions are welcome and appreciated!
76
+
77
+ 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.
78
+
79
+ ## License
80
+
81
+ `rcer_iot_client_pkg` was created by @pedrozavalat. It is licensed under the terms of the MIT license.
82
+
@@ -0,0 +1,40 @@
1
+ rcer_iot_client_pkg/__init__.py,sha256=Uy9WDPvHbwKUs7JeDHgQi_MGuXn6m7lKVniJgj49xOo,186
2
+ rcer_iot_client_pkg/general_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ rcer_iot_client_pkg/general_types/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ rcer_iot_client_pkg/general_types/api/update_thies_data_types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ rcer_iot_client_pkg/general_types/error_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ rcer_iot_client_pkg/general_types/error_types/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ rcer_iot_client_pkg/general_types/error_types/api/update_thies_data_error_types.py,sha256=pbkIZxKTGtTOSVVcOVQ-mINN5mY8JYt5PeFqsyAF-Sg,640
8
+ rcer_iot_client_pkg/general_types/error_types/common/__init__.py,sha256=iICVsiGpW9SZIvUwQb7BxvP_La3NZeyCWCRLqHmJ_l8,214
9
+ rcer_iot_client_pkg/general_types/error_types/common/common_types.py,sha256=w25vajcnX24zIezMG3EjG32dDouV51p-iXtNKz8qjD4,319
10
+ rcer_iot_client_pkg/libs/async_http_client/__init__.py,sha256=LF1KEh5Dhu0j9qZhSBGOsD-PWUFKEvS2oE0eUCyNLd0,279
11
+ rcer_iot_client_pkg/libs/async_http_client/async_http_client.py,sha256=wlOvjThS8wnpyqdcY7HbeEx1X3eVNKxJ8DuCJtx0wFk,1135
12
+ rcer_iot_client_pkg/libs/async_http_client/async_http_client_contract.py,sha256=PCoTt4R6vFwjvyOTETQ8jEw1uqzAQC3PYiIlg_d7iRI,816
13
+ rcer_iot_client_pkg/libs/async_http_client/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ rcer_iot_client_pkg/libs/async_http_client/clients/aiohttp_client.py,sha256=bU473GBqx1Sj5r4V06E70rGjb4DE7zP8krkPWXmgWyo,1845
15
+ rcer_iot_client_pkg/libs/async_http_client/types/__init__.py,sha256=TnJOBj5R6rVA5XAiId5dpQM6KjMIfi8KJkCCav7n930,154
16
+ rcer_iot_client_pkg/libs/async_http_client/types/async_http_client_types.py,sha256=d-_rHQ21ajTr5AI66fH3-cpJzF8VhSqG0BHbVrlwND8,329
17
+ rcer_iot_client_pkg/libs/ftp_client/__init__.py,sha256=6c6C3_hnZVApYKlRVOlCeEPH8RPnQ22c5Fqqb-hVKp0,179
18
+ rcer_iot_client_pkg/libs/ftp_client/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ rcer_iot_client_pkg/libs/ftp_client/clients/aioftp_client.py,sha256=8DaInFB4krJ-U-2spa6A_gUzzWSg4CWR7a_QQ6ng5hk,1127
20
+ rcer_iot_client_pkg/libs/ftp_client/ftp_client.py,sha256=sYaq_ilO1DlDHHMFz4Gt4t7oTx3hXFHQJx9WCoqG7M4,808
21
+ rcer_iot_client_pkg/libs/ftp_client/ftp_client_contract.py,sha256=sISJSMzkCBTdRZT9xpspzQBvo03d80mC4XdmbEd_EwY,314
22
+ rcer_iot_client_pkg/libs/ftp_client/types/__init__.py,sha256=_HaW5-nKNvxfgf7tiGoAI_bZewzneHE9DY_3niwFRKw,143
23
+ rcer_iot_client_pkg/libs/ftp_client/types/ftp_client_types.py,sha256=uNECnvKpc8z-WFE1C-EaYypDqprGmyG25sKDfTvONT4,276
24
+ rcer_iot_client_pkg/libs/zero_dependency/utils/datetime_utils.py,sha256=kD38wC087H3jwTIgrntBajE55cR2ioo_ftPUHiyGs_M,751
25
+ rcer_iot_client_pkg/services/epii/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ rcer_iot_client_pkg/services/epii/api.py,sha256=pSCvYqBQgMvAAww8Orco6Ua7A_TS5rLLcvpuf_VsJrQ,727
27
+ rcer_iot_client_pkg/services/epii/controllers/__init__.py,sha256=giezZrFE8xp9Svfy1GF1ihKlkMe6TQplu_bZNOua5NI,156
28
+ rcer_iot_client_pkg/services/epii/controllers/types/__init__.py,sha256=uhMAjHUFKGeFUfnMeS6ZUBmozkeTcLZbUcuId8oMTgo,250
29
+ rcer_iot_client_pkg/services/epii/controllers/types/update_thies_data_types.py,sha256=0zbNgjln7PXQRjFPAbiNV9n_cXV1H1-XtXMCC_VA4kE,336
30
+ rcer_iot_client_pkg/services/epii/controllers/update_thies_data.py,sha256=GHK0nH9dniFa8sjn3zv0AmYBVfiKnIMhsSvJcAnxgFI,2689
31
+ rcer_iot_client_pkg/services/epii/use_cases/constants.py,sha256=fslc2XtEBs2bwVL1pfXXeDnJw35YLYz35ApXwatplH8,202
32
+ rcer_iot_client_pkg/services/epii/use_cases/types/__init__.py,sha256=c2RRUbjWq2PIqYW8DcxmYDg0gLZ74kmHh3BkeQCVii0,108
33
+ rcer_iot_client_pkg/services/epii/use_cases/types/update_thies_data_types.py,sha256=Rt76-f3tjJpg5VL8sraxXYVhXG_GHlnDBKotqCFuS8o,334
34
+ rcer_iot_client_pkg/services/epii/use_cases/update_thies_data.py,sha256=9nUrw-jgrT_A99k6ZplTY8EsHFwFxtYMNKnUPeCgWlo,4992
35
+ rcer_iot_client_pkg/services/epii/utils/__init__.py,sha256=_IAX_luockBtgJX76SBs3q0IaXU4vC8z8YBy5Dop5TI,139
36
+ rcer_iot_client_pkg/services/epii/utils/update_thies_data_utils.py,sha256=NzWYfZ18Lf7Q7r_1UpVtZTgFr3mzeUlrdpX-Z9XagG4,418
37
+ rcer_iot_client_pkg-0.1.0.dist-info/LICENSE,sha256=NWpf6b38xgBWPBo5HZsCbdfp9hZSliEbRqWQgm0fkOo,1076
38
+ rcer_iot_client_pkg-0.1.0.dist-info/METADATA,sha256=ddJRrZt-P6Vv4UNXKm9_-jHQBPvxR0NCkj8taKuib8Q,2427
39
+ rcer_iot_client_pkg-0.1.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
40
+ rcer_iot_client_pkg-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.2
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any