rcer-iot-client-pkg 0.3.1__tar.gz → 0.5.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.
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/PKG-INFO +34 -1
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/README.md +33 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/pyproject.toml +1 -1
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/general_types/api/update_thies_data_types.py +29 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/general_types/error_types/api/update_thies_data_error_types.py +30 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/general_types/error_types/common/__init__.py +7 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/general_types/error_types/common/common_types.py +17 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/libs/ftp_client/__init__.py +4 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/ftp_client/clients/aioftp_client.py +20 -12
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/ftp_client/ftp_client.py +3 -3
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/libs/ftp_client/ftp_client_contract.py +13 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/libs/ftp_client/types/__init__.py +3 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/ftp_client/types/ftp_client_types.py +4 -6
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/libs/sharepoint_client/__init__.py +15 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/libs/sharepoint_client/clients/sharepoint_rest_api.py +134 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/libs/sharepoint_client/sharepoint_client.py +34 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/libs/sharepoint_client/sharepoint_client_contract.py +21 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/libs/sharepoint_client/types/sharepoint_client_types.py +25 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/services/epii/api.py +24 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/services/epii/constants/update_thies_data_constants.py +5 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/services/epii/controllers/types/update_thies_data_types.py +2 -4
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/services/epii/controllers/update_thies_data.py +40 -9
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/services/epii/use_cases/types/__init__.py +7 -0
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/services/epii/use_cases/types/update_thies_data_types.py +32 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/services/epii/use_cases/update_thies_data.py +60 -62
- rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/services/epii/utils/__init__.py +3 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/services/epii/utils/update_thies_data_utils.py +1 -1
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/general_types/api/update_thies_data_types.py +0 -0
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/general_types/error_types/api/update_thies_data_error_types.py +0 -19
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/general_types/error_types/common/__init__.py +0 -7
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/general_types/error_types/common/common_types.py +0 -13
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/libs/ftp_client/__init__.py +0 -4
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/libs/ftp_client/ftp_client_contract.py +0 -13
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/libs/ftp_client/types/__init__.py +0 -3
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/services/epii/api.py +0 -24
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/services/epii/use_cases/constants.py +0 -4
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/services/epii/use_cases/types/__init__.py +0 -3
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/services/epii/use_cases/types/update_thies_data_types.py +0 -17
- rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/services/epii/utils/__init__.py +0 -3
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/LICENSE +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/general_types/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/general_types/api/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/general_types/error_types/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/general_types/error_types/api/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/async_http_client/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/async_http_client/async_http_client.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/async_http_client/async_http_client_contract.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/async_http_client/clients/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/async_http_client/clients/aiohttp_client.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/async_http_client/types/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/async_http_client/types/async_http_client_types.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/ftp_client/clients/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/libs/zero_dependency/utils/datetime_utils.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/services/epii/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/services/epii/controllers/__init__.py +0 -0
- {rcer_iot_client_pkg-0.3.1 → rcer_iot_client_pkg-0.5.0}/src/rcer_iot_client_pkg/services/epii/controllers/types/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: rcer_iot_client_pkg
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
4
4
|
Summary: A client library for IoT projects in the RCER initiative
|
5
5
|
License: MIT
|
6
6
|
Author: pedropablozavalat
|
@@ -30,6 +30,39 @@ This library provides a robust and efficient client for interacting with IoT dev
|
|
30
30
|
pip install rcer_iot_client_pkg
|
31
31
|
```
|
32
32
|
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
### Initialize the EPii API Client
|
36
|
+
To start using the library, you need to create an `EpiiAPI` client instance:
|
37
|
+
|
38
|
+
```python
|
39
|
+
from rcer_iot_client_pkg import EpiiAPI
|
40
|
+
|
41
|
+
api_client = EpiiAPI()
|
42
|
+
```
|
43
|
+
|
44
|
+
### Update THIES Data Logger Files
|
45
|
+
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:
|
46
|
+
|
47
|
+
```python
|
48
|
+
import asyncio
|
49
|
+
|
50
|
+
async def update_thies_data():
|
51
|
+
response = await api_client.update_thies_data(
|
52
|
+
ftp_port=PORT,
|
53
|
+
ftp_host=LOCAL_HOST,
|
54
|
+
ftp_password=PASSWORD,
|
55
|
+
ftp_user=USER
|
56
|
+
)
|
57
|
+
return response
|
58
|
+
|
59
|
+
asyncio.run(update_thies_data())
|
60
|
+
```
|
61
|
+
|
62
|
+
**Notes:**
|
63
|
+
- Store sensitive data like `PASSWORD` and `USER` securely, e.g., in environment variables or a secrets file.
|
64
|
+
- Ensure `asyncio` is installed to run concurrent code with `EpiiAPI` methods.
|
65
|
+
|
33
66
|
## Development
|
34
67
|
|
35
68
|
This project includes a `Makefile` to simplify common tasks. Below are the available commands:
|
@@ -9,6 +9,39 @@ This library provides a robust and efficient client for interacting with IoT dev
|
|
9
9
|
pip install rcer_iot_client_pkg
|
10
10
|
```
|
11
11
|
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
### Initialize the EPii API Client
|
15
|
+
To start using the library, you need to create an `EpiiAPI` client instance:
|
16
|
+
|
17
|
+
```python
|
18
|
+
from rcer_iot_client_pkg import EpiiAPI
|
19
|
+
|
20
|
+
api_client = EpiiAPI()
|
21
|
+
```
|
22
|
+
|
23
|
+
### Update THIES Data Logger Files
|
24
|
+
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:
|
25
|
+
|
26
|
+
```python
|
27
|
+
import asyncio
|
28
|
+
|
29
|
+
async def update_thies_data():
|
30
|
+
response = await api_client.update_thies_data(
|
31
|
+
ftp_port=PORT,
|
32
|
+
ftp_host=LOCAL_HOST,
|
33
|
+
ftp_password=PASSWORD,
|
34
|
+
ftp_user=USER
|
35
|
+
)
|
36
|
+
return response
|
37
|
+
|
38
|
+
asyncio.run(update_thies_data())
|
39
|
+
```
|
40
|
+
|
41
|
+
**Notes:**
|
42
|
+
- Store sensitive data like `PASSWORD` and `USER` securely, e.g., in environment variables or a secrets file.
|
43
|
+
- Ensure `asyncio` is installed to run concurrent code with `EpiiAPI` methods.
|
44
|
+
|
12
45
|
## Development
|
13
46
|
|
14
47
|
This project includes a `Makefile` to simplify common tasks. Below are the available commands:
|
rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/general_types/api/update_thies_data_types.py
ADDED
@@ -0,0 +1,29 @@
|
|
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_port: int
|
22
|
+
ftp_host: str
|
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
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class ThiesConnectionError(Exception):
|
2
|
+
"""Raised when unable to connect to the THIES FTP Server"""
|
3
|
+
|
4
|
+
def __init__(self, *args, reason):
|
5
|
+
super().__init__(*args, reason)
|
6
|
+
self.reason = reason
|
7
|
+
|
8
|
+
def __str__(self):
|
9
|
+
return "Unable to connect to THIES FTP Server. " + self.reason.__str__()
|
10
|
+
|
11
|
+
|
12
|
+
class ThiesFetchingError(Exception):
|
13
|
+
"""Raised when no files are found to upload to the server."""
|
14
|
+
|
15
|
+
def __init__(self, *args, reason):
|
16
|
+
super().__init__(*args, reason)
|
17
|
+
self.reason = reason
|
18
|
+
|
19
|
+
def __str__(self):
|
20
|
+
return (
|
21
|
+
"An error ocurred while retrieving files from THIES FTP Server. "
|
22
|
+
+ self.reason.__str__()
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
class SharePointFetchingError(Exception):
|
27
|
+
"""Raised when there is an error fetching file names from the RCER cloud."""
|
28
|
+
|
29
|
+
def __str__(self):
|
30
|
+
return "An error occurred while retrieving file names from the RCER cloud"
|
rcer_iot_client_pkg-0.5.0/src/rcer_iot_client_pkg/general_types/error_types/common/common_types.py
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class EmptyDataError(Exception):
|
2
|
+
def __init__(self, *args, reason):
|
3
|
+
super().__init__(*args, reason)
|
4
|
+
self.reason = reason
|
5
|
+
|
6
|
+
def __str__(self):
|
7
|
+
return "The data provided is empty. " + self.reason.__str__()
|
8
|
+
|
9
|
+
|
10
|
+
class SharepointClientError(Exception):
|
11
|
+
def __str__(self):
|
12
|
+
return "SharePoint API REST Client initialization fails."
|
13
|
+
|
14
|
+
|
15
|
+
class FtpClientError(Exception):
|
16
|
+
def __str__(self):
|
17
|
+
return "Ftp Client initialization fails."
|
@@ -1,37 +1,45 @@
|
|
1
1
|
from aioftp import Client
|
2
|
-
|
2
|
+
from aioftp.errors import StatusCodeError
|
3
3
|
from rcer_iot_client_pkg.libs.ftp_client.ftp_client_contract import (
|
4
4
|
FTPClientContract,
|
5
5
|
)
|
6
6
|
from rcer_iot_client_pkg.libs.ftp_client.types.ftp_client_types import (
|
7
7
|
FtpClientInitArgs,
|
8
|
-
|
9
|
-
|
8
|
+
FtpListFilesArgs,
|
9
|
+
FtpReadFileArgs,
|
10
10
|
)
|
11
11
|
|
12
12
|
|
13
13
|
class AioFTPClient(FTPClientContract):
|
14
14
|
def __init__(self, args: FtpClientInitArgs) -> None:
|
15
|
-
self.host = args.
|
16
|
-
self.port = args.
|
17
|
-
self.password = args.
|
18
|
-
self.user = args.
|
15
|
+
self.host = args.config.ftp_host
|
16
|
+
self.port = args.config.ftp_port
|
17
|
+
self.password = args.config.ftp_password
|
18
|
+
self.user = args.config.ftp_user
|
19
19
|
self.client = Client()
|
20
20
|
|
21
21
|
async def _async_start(self) -> None:
|
22
|
-
try:
|
22
|
+
try:
|
23
23
|
await self.client.connect(host=self.host, port=self.port)
|
24
|
+
except OSError:
|
25
|
+
raise ConnectionRefusedError(
|
26
|
+
f"{self.host}:{self.port} isn't active. "
|
27
|
+
"Please ensure the server is running and accessible."
|
28
|
+
)
|
29
|
+
try:
|
24
30
|
await self.client.login(user=self.user, password=self.password)
|
25
|
-
except
|
26
|
-
raise
|
31
|
+
except StatusCodeError:
|
32
|
+
raise ConnectionAbortedError(
|
33
|
+
"Authentication failed. Please verify your credentials and try again."
|
34
|
+
)
|
27
35
|
|
28
|
-
async def list_files(self, args:
|
36
|
+
async def list_files(self, args: FtpListFilesArgs) -> list[str]:
|
29
37
|
await self._async_start()
|
30
38
|
return [
|
31
39
|
path.name async for path, _ in self.client.list(args.path, recursive=False)
|
32
40
|
]
|
33
41
|
|
34
|
-
async def read_file(self, args:
|
42
|
+
async def read_file(self, args: FtpReadFileArgs) -> bytes:
|
35
43
|
await self._async_start()
|
36
44
|
async with self.client.download_stream(args.file_path) as stream:
|
37
45
|
return await stream.read()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from .clients.aioftp_client import AioFTPClient
|
2
2
|
from .ftp_client_contract import FTPClientContract
|
3
|
-
from .types.ftp_client_types import FtpClientInitArgs,
|
3
|
+
from .types.ftp_client_types import FtpClientInitArgs, FtpListFilesArgs, FtpReadFileArgs
|
4
4
|
|
5
5
|
|
6
6
|
class FTPClient(FTPClientContract):
|
@@ -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:
|
18
|
+
def list_files(self, args: FtpListFilesArgs) -> list[str]:
|
19
19
|
return self.client_obj.list_files(args)
|
20
20
|
|
21
|
-
def read_file(self, args:
|
21
|
+
def read_file(self, args: FtpReadFileArgs) -> bytes:
|
22
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 FtpListFilesArgs, FtpReadFileArgs
|
4
|
+
|
5
|
+
|
6
|
+
class FTPClientContract(ABC):
|
7
|
+
@abstractmethod
|
8
|
+
def list_files(self, args: FtpListFilesArgs) -> list[str]:
|
9
|
+
pass
|
10
|
+
|
11
|
+
@abstractmethod
|
12
|
+
def read_file(self, args: FtpReadFileArgs) -> bytes:
|
13
|
+
pass
|
@@ -1,20 +1,18 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
|
+
from typing import Any
|
2
3
|
|
3
4
|
|
4
5
|
@dataclass
|
5
6
|
class FtpClientInitArgs:
|
6
|
-
|
7
|
-
user: str
|
8
|
-
password: str
|
7
|
+
config: Any
|
9
8
|
client_name: str = "aioftp_client"
|
10
|
-
port: int = 21
|
11
9
|
|
12
10
|
|
13
11
|
@dataclass
|
14
|
-
class
|
12
|
+
class FtpListFilesArgs:
|
15
13
|
path: str
|
16
14
|
|
17
15
|
|
18
16
|
@dataclass
|
19
|
-
class
|
17
|
+
class FtpReadFileArgs:
|
20
18
|
file_path: str
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from .sharepoint_client import SharepointClient
|
2
|
+
from .types.sharepoint_client_types import (
|
3
|
+
SharepointClientInitArgs,
|
4
|
+
SpListFilesArgs,
|
5
|
+
SpListFoldersArgs,
|
6
|
+
SpUploadFileArgs,
|
7
|
+
)
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"SharepointClientInitArgs",
|
11
|
+
"SharepointClient",
|
12
|
+
"SpListFilesArgs",
|
13
|
+
"SpListFoldersArgs",
|
14
|
+
"SpUploadFileArgs",
|
15
|
+
]
|
@@ -0,0 +1,134 @@
|
|
1
|
+
import os
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from aiohttp import ClientError, ClientSession
|
5
|
+
from dotenv import load_dotenv
|
6
|
+
|
7
|
+
from rcer_iot_client_pkg.libs.sharepoint_client.sharepoint_client_contract import (
|
8
|
+
SharepointClientContract,
|
9
|
+
)
|
10
|
+
from rcer_iot_client_pkg.libs.sharepoint_client.types.sharepoint_client_types import (
|
11
|
+
SpListFilesArgs,
|
12
|
+
SpListFoldersArgs,
|
13
|
+
SpUploadFileArgs,
|
14
|
+
SharepointClientInitArgs,
|
15
|
+
)
|
16
|
+
|
17
|
+
load_dotenv()
|
18
|
+
|
19
|
+
|
20
|
+
class SharepointRestAPI(SharepointClientContract):
|
21
|
+
def __init__(self, args: SharepointClientInitArgs):
|
22
|
+
self.session: ClientSession | None = None
|
23
|
+
self.base_headers = {}
|
24
|
+
self.credentials = {}
|
25
|
+
self.base_url = ""
|
26
|
+
self.tenant_id = args.config.sharepoint_tenant_id
|
27
|
+
self.tenant_name = args.config.sharepoint_tenant_name
|
28
|
+
self.client_secret = args.config.sharepoint_client_secret
|
29
|
+
self.client_id = args.config.sharepoint_client_id
|
30
|
+
self.site_name = args.config.sharepoint_site_name
|
31
|
+
|
32
|
+
async def _load_form_digest_value(self) -> str:
|
33
|
+
try:
|
34
|
+
response = await self.session.post("contextinfo")
|
35
|
+
response_json = await response.json()
|
36
|
+
return response_json["FormDigestValue"]
|
37
|
+
except ClientError as error:
|
38
|
+
raise ConnectionError(error) from error
|
39
|
+
|
40
|
+
async def _load_credentials(self) -> dict:
|
41
|
+
resource_base = "00000003-0000-0ff1-ce00-000000000000"
|
42
|
+
resource = f"{resource_base}/{self.tenant_name}.sharepoint.com@{self.tenant_id}"
|
43
|
+
url = f"https://accounts.accesscontrol.windows.net/{self.tenant_id}/tokens/OAuth/2"
|
44
|
+
payload = {
|
45
|
+
"grant_type": "client_credentials",
|
46
|
+
"client_id": f"{self.client_id}@{self.tenant_id}",
|
47
|
+
"client_secret": self.client_secret,
|
48
|
+
"resource": resource,
|
49
|
+
}
|
50
|
+
headers = {
|
51
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
52
|
+
}
|
53
|
+
|
54
|
+
async with ClientSession() as session:
|
55
|
+
# Load access token
|
56
|
+
response = await session.post(url, data=payload, headers=headers)
|
57
|
+
if response.status != 200:
|
58
|
+
raise ClientError(
|
59
|
+
f"Failed to fetch credentials: {response.status}, {await response.text()}"
|
60
|
+
)
|
61
|
+
response_json = await response.json()
|
62
|
+
|
63
|
+
return {
|
64
|
+
"access_token": response_json["access_token"],
|
65
|
+
}
|
66
|
+
|
67
|
+
async def __aenter__(self) -> "SharepointRestAPI":
|
68
|
+
self.credentials = await self._load_credentials()
|
69
|
+
site_url = f"https://{self.tenant_name}.sharepoint.com"
|
70
|
+
|
71
|
+
self.base_headers = {
|
72
|
+
"Authorization": f"Bearer {self.credentials['access_token']}",
|
73
|
+
"Accept": "application/json",
|
74
|
+
"Content-Type": "application/json",
|
75
|
+
}
|
76
|
+
self.base_url = f"{site_url}/sites/{self.site_name}/_api/"
|
77
|
+
self.session = ClientSession(headers=self.base_headers, base_url=self.base_url)
|
78
|
+
return self
|
79
|
+
|
80
|
+
async def __aexit__(
|
81
|
+
self, _exc_type: type[BaseException], _exc_val: BaseException, _exc_tb: Any
|
82
|
+
) -> None:
|
83
|
+
await self.session.close()
|
84
|
+
|
85
|
+
async def list_files(self, args: SpListFilesArgs) -> list:
|
86
|
+
try:
|
87
|
+
folder_relative_url = (
|
88
|
+
f"GetFolderByServerRelativeUrl('{args.folder_relative_url}')"
|
89
|
+
)
|
90
|
+
endpoint = f"web/{folder_relative_url}/Files"
|
91
|
+
response = await self.session.get(endpoint.lstrip("/"))
|
92
|
+
response.raise_for_status()
|
93
|
+
response_json = await response.json()
|
94
|
+
return response_json
|
95
|
+
except ClientError as error:
|
96
|
+
raise ConnectionError(error) from error
|
97
|
+
|
98
|
+
async def list_folders(self, args: SpListFoldersArgs) -> list:
|
99
|
+
try:
|
100
|
+
folder_relative_url = (
|
101
|
+
f"GetFolderByServerRelativeUrl('{args.folder_relative_url}')"
|
102
|
+
)
|
103
|
+
endpoint = f"web/{folder_relative_url}/Folder"
|
104
|
+
response = await self.session.get(endpoint.lstrip("/"))
|
105
|
+
response.raise_for_status()
|
106
|
+
return await response.json()
|
107
|
+
except ClientError as error:
|
108
|
+
raise ConnectionError(error) from error
|
109
|
+
|
110
|
+
async def upload_file(self, args: SpUploadFileArgs) -> dict:
|
111
|
+
try:
|
112
|
+
# Load form digest value
|
113
|
+
form_digest_value = await self._load_form_digest_value()
|
114
|
+
headers = {
|
115
|
+
**self.base_headers,
|
116
|
+
"X-RequestDigest": form_digest_value,
|
117
|
+
"Content-Type": "application/octet-stream",
|
118
|
+
}
|
119
|
+
# Upload the file in the requested folder
|
120
|
+
folder_relative_url = (
|
121
|
+
f"GetFolderByServerRelativeUrl('{args.folder_relative_url}')"
|
122
|
+
)
|
123
|
+
# Read the file
|
124
|
+
source_file_path = os.path.basename(args.file_path)
|
125
|
+
with open(source_file_path, "rb") as file:
|
126
|
+
data = file.read()
|
127
|
+
|
128
|
+
endpoint = f"web/{folder_relative_url}/Files/add(url='{source_file_path}',overwrite=false)"
|
129
|
+
response = await self.session.post(endpoint, data=data, headers=headers)
|
130
|
+
|
131
|
+
response.raise_for_status()
|
132
|
+
return await response.json()
|
133
|
+
except ClientError as error:
|
134
|
+
raise ConnectionError(error) from error
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from .clients.sharepoint_rest_api import SharepointRestAPI
|
2
|
+
from .sharepoint_client_contract import SharepointClientContract
|
3
|
+
from .types.sharepoint_client_types import (
|
4
|
+
SharepointClientInitArgs,
|
5
|
+
SpListFilesArgs,
|
6
|
+
SpListFoldersArgs,
|
7
|
+
SpUploadFileArgs,
|
8
|
+
)
|
9
|
+
|
10
|
+
|
11
|
+
class SharepointClient(SharepointClientContract):
|
12
|
+
CLIENTS = {"sharepoint_rest_api"}
|
13
|
+
|
14
|
+
def __init__(self, args: SharepointClientInitArgs):
|
15
|
+
if args.client_name not in SharepointClient.CLIENTS:
|
16
|
+
msg = f"Unsupported client {args.client_name}"
|
17
|
+
raise KeyError(msg)
|
18
|
+
elif args.client_name == "sharepoint_rest_api":
|
19
|
+
self.client_obj = SharepointRestAPI(args)
|
20
|
+
|
21
|
+
async def __aenter__(self):
|
22
|
+
return await self.client_obj.__aenter__()
|
23
|
+
|
24
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
25
|
+
await self.client_obj.__aexit__(exc_type, exc_val, exc_tb)
|
26
|
+
|
27
|
+
async def list_files(self, args: SpListFilesArgs) -> list:
|
28
|
+
return await self.client_obj.list_files(args)
|
29
|
+
|
30
|
+
async def list_folders(self, args: SpListFoldersArgs) -> list:
|
31
|
+
return self.client_obj.list_files(args)
|
32
|
+
|
33
|
+
async def upload_file(self, args: SpUploadFileArgs) -> dict:
|
34
|
+
return self.client_obj.upload_file(args)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
from .types.sharepoint_client_types import (
|
4
|
+
SpListFilesArgs,
|
5
|
+
SpListFoldersArgs,
|
6
|
+
SpUploadFileArgs,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
class SharepointClientContract(ABC):
|
11
|
+
@abstractmethod
|
12
|
+
async def list_files(self, args: SpListFilesArgs) -> list:
|
13
|
+
pass
|
14
|
+
|
15
|
+
@abstractmethod
|
16
|
+
async def list_folders(self, args: SpListFoldersArgs) -> list:
|
17
|
+
pass
|
18
|
+
|
19
|
+
@abstractmethod
|
20
|
+
async def upload_file(self, args: SpUploadFileArgs) -> dict:
|
21
|
+
pass
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
|
5
|
+
@dataclass
|
6
|
+
class SharepointClientInitArgs:
|
7
|
+
config: Any
|
8
|
+
client_name: str = "sharepoint_rest_api"
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class SpListFilesArgs:
|
13
|
+
folder_relative_url: str
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass
|
17
|
+
class SpListFoldersArgs:
|
18
|
+
folder_relative_url: str
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class SpUploadFileArgs:
|
23
|
+
file_path: str
|
24
|
+
folder_relative_url: str
|
25
|
+
file_content: bytes = bytes()
|
@@ -0,0 +1,24 @@
|
|
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 rcer_iot_client_pkg.general_types.api.update_thies_data_types import EpiiAPIConfig
|
6
|
+
|
7
|
+
|
8
|
+
class EpiiAPI:
|
9
|
+
"""
|
10
|
+
EpiiAPI is a service class that provides methods to interact with Patagonia Center system.
|
11
|
+
"""
|
12
|
+
|
13
|
+
async def update_thies_data(self, config: EpiiAPIConfig) -> Dict[str, Any]:
|
14
|
+
"""
|
15
|
+
This method establishes a connection to an FTP server using the provided
|
16
|
+
credentials and updates data related to THIES Data Logger.
|
17
|
+
Args:
|
18
|
+
config (EpiiAPIConfig): configuration class for FTP Server and Microsoft SharePoint credentials.
|
19
|
+
Returns:
|
20
|
+
response (dict): A dictionary representation of the API response.
|
21
|
+
"""
|
22
|
+
controller = UpdateThiesDataController(UpdateThiesDataControllerInput(config))
|
23
|
+
response = await controller.execute()
|
24
|
+
return response.__dict__
|
@@ -0,0 +1,5 @@
|
|
1
|
+
SHAREPOINT_BASE_URL = "/sites/uc365_CentrosyEstacionesRegionalesUC/Shared Documents/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,13 +1,11 @@
|
|
1
1
|
from dataclasses import dataclass, field
|
2
2
|
from typing import Dict
|
3
|
+
from rcer_iot_client_pkg.general_types.api.update_thies_data_types import EpiiAPIConfig
|
3
4
|
|
4
5
|
|
5
6
|
@dataclass
|
6
7
|
class UpdateThiesDataControllerInput:
|
7
|
-
|
8
|
-
ftp_port: str
|
9
|
-
ftp_user: str
|
10
|
-
ftp_password: str
|
8
|
+
config: EpiiAPIConfig
|
11
9
|
|
12
10
|
|
13
11
|
@dataclass
|
@@ -1,12 +1,14 @@
|
|
1
1
|
from http import HTTPStatus
|
2
2
|
|
3
3
|
from rcer_iot_client_pkg.general_types.error_types.api.update_thies_data_error_types import (
|
4
|
-
|
5
|
-
|
4
|
+
SharePointFetchingError,
|
5
|
+
ThiesConnectionError,
|
6
|
+
ThiesFetchingError,
|
6
7
|
)
|
7
8
|
from rcer_iot_client_pkg.general_types.error_types.common.common_types import (
|
9
|
+
EmptyDataError,
|
8
10
|
FtpClientError,
|
9
|
-
|
11
|
+
SharepointClientError,
|
10
12
|
)
|
11
13
|
from rcer_iot_client_pkg.services.epii.controllers.types.update_thies_data_types import (
|
12
14
|
UpdateThiesDataControllerInput,
|
@@ -14,6 +16,8 @@ from rcer_iot_client_pkg.services.epii.controllers.types.update_thies_data_types
|
|
14
16
|
)
|
15
17
|
from rcer_iot_client_pkg.services.epii.use_cases.types import (
|
16
18
|
UpdateThiesDataUseCaseInput,
|
19
|
+
SharepointConfig,
|
20
|
+
FtpClientConfig,
|
17
21
|
)
|
18
22
|
from rcer_iot_client_pkg.services.epii.use_cases.update_thies_data import (
|
19
23
|
UpdateThiesDataUseCase,
|
@@ -23,7 +27,21 @@ from rcer_iot_client_pkg.services.epii.use_cases.update_thies_data import (
|
|
23
27
|
class UpdateThiesDataController:
|
24
28
|
def __init__(self, input: UpdateThiesDataControllerInput):
|
25
29
|
self.use_case = UpdateThiesDataUseCase(
|
26
|
-
UpdateThiesDataUseCaseInput(
|
30
|
+
UpdateThiesDataUseCaseInput(
|
31
|
+
ftp_config=FtpClientConfig(
|
32
|
+
ftp_host=input.config.ftp_host,
|
33
|
+
ftp_password=input.config.ftp_password,
|
34
|
+
ftp_port=input.config.ftp_port,
|
35
|
+
ftp_user=input.config.ftp_user,
|
36
|
+
),
|
37
|
+
sharepoint_config=SharepointConfig(
|
38
|
+
sharepoint_client_id=input.config.sharepoint_client_id,
|
39
|
+
sharepoint_client_secret=input.config.sharepoint_client_secret,
|
40
|
+
sharepoint_site_name=input.config.sharepoint_site_name,
|
41
|
+
sharepoint_tenant_name=input.config.sharepoint_tenant_name,
|
42
|
+
sharepoint_tenant_id=input.config.sharepoint_tenant_id,
|
43
|
+
),
|
44
|
+
)
|
27
45
|
)
|
28
46
|
|
29
47
|
async def execute(self) -> UpdateThiesDataControllerOutput:
|
@@ -34,6 +52,11 @@ class UpdateThiesDataController:
|
|
34
52
|
status=HTTPStatus.OK.value,
|
35
53
|
metadata={"data": data},
|
36
54
|
)
|
55
|
+
except EmptyDataError:
|
56
|
+
return UpdateThiesDataControllerOutput(
|
57
|
+
message="No files to upload", status=HTTPStatus.NO_CONTENT
|
58
|
+
)
|
59
|
+
|
37
60
|
except (AttributeError, NameError, ValueError) as error:
|
38
61
|
return UpdateThiesDataControllerOutput(
|
39
62
|
message="An unexpected error occurred during use case initialization.",
|
@@ -47,22 +70,30 @@ class UpdateThiesDataController:
|
|
47
70
|
metadata={"error": error.__str__()},
|
48
71
|
)
|
49
72
|
|
50
|
-
except
|
73
|
+
except SharepointClientError as error:
|
51
74
|
return UpdateThiesDataControllerOutput(
|
52
|
-
message="
|
75
|
+
message="Sharepoint Client initialization fails.",
|
53
76
|
status=HTTPStatus.BAD_REQUEST.value,
|
54
77
|
metadata={"error": error.__str__()},
|
55
78
|
)
|
56
79
|
|
57
|
-
except
|
80
|
+
except SharePointFetchingError as error:
|
58
81
|
return UpdateThiesDataControllerOutput(
|
59
82
|
message="An error occurred while retrieving file names from the RCER cloud",
|
60
83
|
status=HTTPStatus.INTERNAL_SERVER_ERROR.value,
|
61
84
|
metadata={"error": error.__str__()},
|
62
85
|
)
|
63
|
-
|
86
|
+
|
87
|
+
except ThiesFetchingError as error:
|
64
88
|
return UpdateThiesDataControllerOutput(
|
65
|
-
message="
|
89
|
+
message="An error ocurred while retrieving file names from THIES FTP Server.",
|
66
90
|
status=HTTPStatus.NO_CONTENT.value,
|
67
91
|
metadata={"error": error.__str__()},
|
68
92
|
)
|
93
|
+
|
94
|
+
except ThiesConnectionError as error:
|
95
|
+
return UpdateThiesDataControllerOutput(
|
96
|
+
message="Unable to connect to THIES Data Logger FTP Server.",
|
97
|
+
status=HTTPStatus.INTERNAL_SERVER_ERROR.value,
|
98
|
+
metadata={"error": error.__str__()},
|
99
|
+
)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from dataclasses import dataclass, field
|
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
|
20
|
+
|
21
|
+
|
22
|
+
@dataclass
|
23
|
+
class UpdateThiesDataUseCaseInput:
|
24
|
+
ftp_config: FtpClientConfig
|
25
|
+
sharepoint_config: SharepointConfig
|
26
|
+
|
27
|
+
|
28
|
+
@dataclass
|
29
|
+
class UpdateThiesDataUseCaseOutput:
|
30
|
+
message: str
|
31
|
+
status: int = 0
|
32
|
+
metadata: Dict[str, str] = field(default_factory=dict)
|
@@ -1,32 +1,34 @@
|
|
1
1
|
from dotenv import load_dotenv
|
2
2
|
|
3
|
-
import rcer_iot_client_pkg.services.epii.
|
3
|
+
import rcer_iot_client_pkg.services.epii.constants.update_thies_data_constants as c
|
4
4
|
from rcer_iot_client_pkg.general_types.error_types.api.update_thies_data_error_types import (
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
SharePointFetchingError,
|
6
|
+
ThiesConnectionError,
|
7
|
+
ThiesFetchingError,
|
8
8
|
)
|
9
9
|
from rcer_iot_client_pkg.general_types.error_types.common import (
|
10
10
|
EmptyDataError,
|
11
11
|
FtpClientError,
|
12
|
-
|
13
|
-
)
|
14
|
-
from rcer_iot_client_pkg.libs.async_http_client import (
|
15
|
-
AsyncHTTPClient,
|
16
|
-
AsyncHttpClientInitArgs,
|
17
|
-
GetArgs,
|
12
|
+
SharepointClientError,
|
18
13
|
)
|
19
14
|
from rcer_iot_client_pkg.libs.ftp_client import (
|
20
15
|
FTPClient,
|
21
16
|
FtpClientInitArgs,
|
22
|
-
|
23
|
-
|
17
|
+
FtpListFilesArgs,
|
18
|
+
FtpReadFileArgs,
|
19
|
+
)
|
20
|
+
from rcer_iot_client_pkg.libs.sharepoint_client import (
|
21
|
+
SharepointClient,
|
22
|
+
SharepointClientInitArgs,
|
23
|
+
SpListFilesArgs,
|
24
24
|
)
|
25
25
|
from rcer_iot_client_pkg.services.epii.use_cases.types import (
|
26
26
|
UpdateThiesDataUseCaseInput,
|
27
|
+
FtpClientConfig,
|
28
|
+
SharepointConfig,
|
27
29
|
)
|
28
30
|
from rcer_iot_client_pkg.services.epii.utils import (
|
29
|
-
|
31
|
+
parse_execute_response,
|
30
32
|
)
|
31
33
|
|
32
34
|
load_dotenv()
|
@@ -34,91 +36,85 @@ load_dotenv()
|
|
34
36
|
|
35
37
|
class UpdateThiesDataUseCase:
|
36
38
|
def __init__(self, input: UpdateThiesDataUseCaseInput):
|
37
|
-
self.
|
38
|
-
|
39
|
-
|
40
|
-
self.
|
41
|
-
self.sharepoint_client = self._initialize_sharepoint_client()
|
42
|
-
self.thies_ftp_client = self._initialize_thies_ftp_client()
|
39
|
+
self.sharepoint_client = self._initialize_sharepoint_client(
|
40
|
+
input.sharepoint_config
|
41
|
+
)
|
42
|
+
self.thies_ftp_client = self._initialize_thies_ftp_client(input.ftp_config)
|
43
43
|
self.uploading = set()
|
44
44
|
|
45
|
-
def _initialize_sharepoint_client(
|
45
|
+
def _initialize_sharepoint_client(
|
46
|
+
self, config: SharepointConfig
|
47
|
+
) -> SharepointClient:
|
46
48
|
"""Initialize the HTTP client."""
|
47
49
|
try:
|
48
|
-
return
|
49
|
-
|
50
|
-
client_name="aiohttp_client",
|
51
|
-
access_token="temporal-token",
|
52
|
-
base_url="https://graph.microsoft.com/v1.0/",
|
53
|
-
)
|
50
|
+
return SharepointClient(
|
51
|
+
SharepointClientInitArgs(config, client_name="sharepoint_rest_api")
|
54
52
|
)
|
55
53
|
except ConnectionError as error:
|
56
|
-
raise
|
54
|
+
raise SharepointClientError(error)
|
57
55
|
|
58
|
-
def _initialize_thies_ftp_client(self) -> FTPClient:
|
56
|
+
def _initialize_thies_ftp_client(self, config: FtpClientConfig) -> FTPClient:
|
59
57
|
"""Initialize the FTP client."""
|
60
58
|
try:
|
61
|
-
return FTPClient(
|
62
|
-
FtpClientInitArgs(
|
63
|
-
client_name="aioftp_client",
|
64
|
-
host=self.ftp_host,
|
65
|
-
user=self.ftp_user,
|
66
|
-
password=self.ftp_password,
|
67
|
-
port=self.ftp_port,
|
68
|
-
)
|
69
|
-
)
|
59
|
+
return FTPClient(FtpClientInitArgs(config, client_name="aioftp_client"))
|
70
60
|
except RuntimeError as error:
|
71
61
|
raise FtpClientError(error)
|
72
62
|
|
73
|
-
async def fetch_cloud_file_names(self
|
63
|
+
async def fetch_cloud_file_names(self) -> set[str]:
|
74
64
|
"""Fetch file names from the RCER cloud."""
|
65
|
+
|
75
66
|
try:
|
76
67
|
cloud_files = set()
|
77
68
|
async with self.sharepoint_client:
|
78
|
-
for
|
79
|
-
|
80
|
-
|
81
|
-
response = await self.sharepoint_client.get(
|
82
|
-
GetArgs(endpoint=endpoint)
|
69
|
+
for folder in c.SHAREPOINT_THIES_FOLDERS:
|
70
|
+
args = SpListFilesArgs(
|
71
|
+
folder_relative_url=f"{c.SHAREPOINT_BASE_URL}/{folder}"
|
83
72
|
)
|
73
|
+
response = await self.sharepoint_client.list_files(args)
|
84
74
|
cloud_files.update(
|
85
|
-
{f"{
|
75
|
+
{f"{folder}_{item['Name']}" for item in response["value"]}
|
86
76
|
)
|
87
77
|
return cloud_files
|
88
78
|
except ConnectionError as error:
|
89
|
-
raise
|
79
|
+
raise SharePointFetchingError(reason=error)
|
90
80
|
|
91
81
|
async def fetch_thies_file_names(self) -> set[str]:
|
92
82
|
"""Fetch file names from the THIES FTP server."""
|
93
83
|
try:
|
94
84
|
avg_files = await self.thies_ftp_client.list_files(
|
95
|
-
|
85
|
+
FtpListFilesArgs(path=c.FTP_SERVER_PATH_AVG_FILES)
|
96
86
|
)
|
97
87
|
ext_files = await self.thies_ftp_client.list_files(
|
98
|
-
|
88
|
+
FtpListFilesArgs(path=c.FTP_SERVER_PATH_EXT_FILES)
|
99
89
|
)
|
100
90
|
return {f"AVG_{name}" for name in avg_files} | {
|
101
91
|
f"EXT_{name}" for name in ext_files
|
102
92
|
}
|
103
|
-
except
|
104
|
-
raise
|
93
|
+
except ConnectionRefusedError as error:
|
94
|
+
raise ThiesConnectionError(reason=error)
|
95
|
+
except ConnectionAbortedError as error:
|
96
|
+
raise ThiesFetchingError(reason=error)
|
105
97
|
|
106
98
|
async def fetch_thies_file_content(self) -> dict[str, bytes]:
|
107
99
|
"""Fetch the content of files from the THIES FTP server."""
|
108
|
-
|
109
|
-
|
110
|
-
|
100
|
+
try:
|
101
|
+
content_files = {}
|
102
|
+
for file in self.uploading:
|
111
103
|
origin, filename = file.split("_", 1)
|
112
104
|
file_path = (
|
113
|
-
f"{c.
|
105
|
+
f"{c.FTP_SERVER_PATH_AVG_FILES}/{filename}"
|
114
106
|
if origin == "AVG"
|
115
|
-
else f"{c.
|
107
|
+
else f"{c.FTP_SERVER_PATH_EXT_FILES}/{filename}"
|
108
|
+
)
|
109
|
+
content = await self.thies_ftp_client.read_file(
|
110
|
+
FtpReadFileArgs(file_path)
|
116
111
|
)
|
117
|
-
content = await self.thies_ftp_client.read_file(ReadFileArgs(file_path))
|
118
112
|
content_files[filename] = content
|
119
|
-
|
120
|
-
|
121
|
-
|
113
|
+
return content_files
|
114
|
+
except ConnectionRefusedError as error:
|
115
|
+
raise ThiesConnectionError(reason=error)
|
116
|
+
except ConnectionAbortedError as error:
|
117
|
+
raise ThiesFetchingError(reason=error)
|
122
118
|
|
123
119
|
async def execute(self) -> dict:
|
124
120
|
"""Synchronize data from the THIES Center to the cloud."""
|
@@ -126,12 +122,14 @@ class UpdateThiesDataUseCase:
|
|
126
122
|
thies_files = await self.fetch_thies_file_names()
|
127
123
|
except RuntimeError as error:
|
128
124
|
raise FtpClientError(error)
|
129
|
-
|
130
|
-
|
125
|
+
try:
|
126
|
+
cloud_files = await self.fetch_cloud_file_names()
|
127
|
+
except RuntimeError as error:
|
128
|
+
raise SharepointClient(error)
|
131
129
|
self.uploading = thies_files - cloud_files
|
132
130
|
if not self.uploading:
|
133
|
-
raise EmptyDataError
|
131
|
+
raise EmptyDataError(reason="No files to upload.")
|
134
132
|
|
135
133
|
thies_file_contents = await self.fetch_thies_file_content()
|
136
|
-
data =
|
134
|
+
data = parse_execute_response(thies_file_contents)
|
137
135
|
return data
|
rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/general_types/api/update_thies_data_types.py
DELETED
File without changes
|
@@ -1,19 +0,0 @@
|
|
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"
|
rcer_iot_client_pkg-0.3.1/src/rcer_iot_client_pkg/general_types/error_types/common/common_types.py
DELETED
@@ -1,13 +0,0 @@
|
|
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."
|
@@ -1,13 +0,0 @@
|
|
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
|
@@ -1,24 +0,0 @@
|
|
1
|
-
from typing import Dict
|
2
|
-
|
3
|
-
from .controllers.types.update_thies_data_types import UpdateThiesDataControllerInput
|
4
|
-
from .controllers.update_thies_data import UpdateThiesDataController
|
5
|
-
|
6
|
-
|
7
|
-
class EpiiAPI:
|
8
|
-
async def update_thies_data(
|
9
|
-
self,
|
10
|
-
ftp_port: int,
|
11
|
-
ftp_host: str,
|
12
|
-
ftp_password: str,
|
13
|
-
ftp_user: str,
|
14
|
-
) -> Dict[str, any]:
|
15
|
-
controller = UpdateThiesDataController(
|
16
|
-
UpdateThiesDataControllerInput(
|
17
|
-
ftp_port=ftp_port,
|
18
|
-
ftp_host=ftp_host,
|
19
|
-
ftp_password=ftp_password,
|
20
|
-
ftp_user=ftp_user,
|
21
|
-
)
|
22
|
-
)
|
23
|
-
response = await controller.execute()
|
24
|
-
return response.__dict__
|
@@ -1,17 +0,0 @@
|
|
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)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|