saviialib 1.4.0__py3-none-any.whl → 1.5.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.
Potentially problematic release.
This version of saviialib might be problematic. Click here for more details.
- saviialib/__init__.py +2 -0
- saviialib/general_types/api/saviia_api_types.py +1 -0
- saviialib/general_types/api/saviia_netcamera_api_types.py +11 -0
- saviialib/general_types/api/saviia_shakes_api_types.py +21 -0
- saviialib/general_types/error_types/api/saviia_api_error_types.py +8 -0
- saviialib/general_types/error_types/api/saviia_netcamera_error_types.py +7 -0
- saviialib/general_types/error_types/common/common_types.py +9 -0
- saviialib/libs/directory_client/client/os_client.py +1 -1
- saviialib/libs/ffmpeg_client/__init__.py +8 -0
- saviialib/libs/ffmpeg_client/clients/ffmpeg_asyncio_client.py +101 -0
- saviialib/libs/ffmpeg_client/ffmpeg_client.py +25 -0
- saviialib/libs/ffmpeg_client/ffmpeg_client_contract.py +12 -0
- saviialib/libs/ffmpeg_client/types/ffmpeg_client_types.py +28 -0
- saviialib/libs/sftp_client/__init__.py +8 -0
- saviialib/libs/sftp_client/clients/asyncssh_sftp_client.py +83 -0
- saviialib/libs/sftp_client/sftp_client.py +26 -0
- saviialib/libs/sftp_client/sftp_client_contract.py +13 -0
- saviialib/libs/sftp_client/types/sftp_client_types.py +24 -0
- saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py +1 -0
- saviialib/libs/zero_dependency/utils/strings_utils.py +5 -0
- saviialib/services/backup/api.py +1 -1
- saviialib/services/netcamera/api.py +30 -0
- saviialib/services/netcamera/controllers/get_media_files.py +40 -0
- saviialib/services/netcamera/controllers/types/get_media_files_types.py +16 -0
- saviialib/services/netcamera/use_cases/get_media_files.py +76 -0
- saviialib/services/netcamera/use_cases/types/get_media_files_types.py +18 -0
- saviialib/services/shakes/__init__.py +0 -0
- saviialib/services/shakes/api.py +31 -0
- saviialib/services/shakes/controllers/get_miniseed_files.py +48 -0
- saviialib/services/shakes/controllers/types/get_miniseed_files_types.py +16 -0
- saviialib/services/shakes/use_cases/get_miniseed_files.py +79 -0
- saviialib/services/shakes/use_cases/types/get_miniseed_files_types.py +18 -0
- saviialib/services/shakes/use_cases/utils/get_miniseed_files_utils.py +11 -0
- saviialib/services/thies/use_cases/components/create_thies_statistics_file.py +2 -2
- saviialib/services/thies/use_cases/types/update_thies_data_types.py +4 -1
- saviialib/services/thies/use_cases/update_thies_data.py +4 -4
- saviialib-1.5.0.dist-info/METADATA +126 -0
- {saviialib-1.4.0.dist-info → saviialib-1.5.0.dist-info}/RECORD +40 -14
- saviialib-1.4.0.dist-info/METADATA +0 -124
- {saviialib-1.4.0.dist-info → saviialib-1.5.0.dist-info}/WHEEL +0 -0
- {saviialib-1.4.0.dist-info → saviialib-1.5.0.dist-info}/licenses/LICENSE +0 -0
saviialib/__init__.py
CHANGED
|
@@ -12,6 +12,8 @@ from saviialib.services.thies.api import SaviiaThiesAPI
|
|
|
12
12
|
from saviialib.general_types.api.saviia_thies_api_types import SaviiaThiesConfig
|
|
13
13
|
from saviialib.general_types.api.saviia_backup_api_types import SaviiaBackupConfig
|
|
14
14
|
|
|
15
|
+
__all__ = ["SaviiaAPI", "SaviiaAPIConfig"]
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
class SaviiaAPI:
|
|
17
19
|
API_REGISTRY: Dict[str, Type] = {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from logging import Logger
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class SaviiaShakesConfig:
|
|
7
|
+
"""
|
|
8
|
+
Configuration for Raspberry shakes activities as Miniseed extraction, photo record and video record.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
sftp_user (str): Username for SFTP Client connection
|
|
12
|
+
sftp_password (str): Password for SFTP Client connection
|
|
13
|
+
sftp_port (str): SFTP Server Port. Default port is 22.
|
|
14
|
+
ssh_key_path (str): Path to the SSH Private key for client-side authentication.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
sftp_user: str
|
|
18
|
+
sftp_password: str
|
|
19
|
+
ssh_key_path: str
|
|
20
|
+
logger: Logger
|
|
21
|
+
sftp_port: int = 22
|
|
@@ -103,3 +103,11 @@ class BackupEmptyError(Exception):
|
|
|
103
103
|
|
|
104
104
|
def __str__(self):
|
|
105
105
|
return "The local backup folder is empty. "
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ShakesNoContentError(Exception):
|
|
109
|
+
def __init__(self, *args):
|
|
110
|
+
super().__init__(*args)
|
|
111
|
+
|
|
112
|
+
def __str__(self):
|
|
113
|
+
return "All the miniSEED files have been downloaded and are in the local directory."
|
|
@@ -15,3 +15,12 @@ class SharepointClientError(Exception):
|
|
|
15
15
|
class FtpClientError(Exception):
|
|
16
16
|
def __str__(self):
|
|
17
17
|
return "Ftp Client initialization fails."
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SftpClientError(Exception):
|
|
21
|
+
def __init__(self, *args, reason):
|
|
22
|
+
super().__init__(*args, reason)
|
|
23
|
+
self.reason = reason
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
return "SFTP Client initialization fails." + self.reason.__str__()
|
|
@@ -19,7 +19,7 @@ class OsClient(DirectoryClientContract):
|
|
|
19
19
|
return await asyncio.to_thread(os.listdir, path)
|
|
20
20
|
|
|
21
21
|
@staticmethod
|
|
22
|
-
async def isdir(path: str) ->
|
|
22
|
+
async def isdir(path: str) -> bool:
|
|
23
23
|
return await asyncio.to_thread(os.path.isdir, path)
|
|
24
24
|
|
|
25
25
|
@staticmethod
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from saviialib.libs.ffmpeg_client.types.ffmpeg_client_types import (
|
|
2
|
+
FfmpegClientInitArgs,
|
|
3
|
+
RecordPhotoArgs,
|
|
4
|
+
RecordVideoArgs,
|
|
5
|
+
)
|
|
6
|
+
from saviialib.libs.ffmpeg_client.ffmpeg_client_contract import FfmpegClientContract
|
|
7
|
+
from typing import List
|
|
8
|
+
import asyncio
|
|
9
|
+
import shutil
|
|
10
|
+
|
|
11
|
+
from saviialib.libs.directory_client import DirectoryClient, DirectoryClientArgs
|
|
12
|
+
from saviialib.libs.zero_dependency.utils.datetime_utils import today, datetime_to_str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FfmpegAsyncioClient(FfmpegClientContract):
|
|
16
|
+
def __init__(self, args: FfmpegClientInitArgs) -> None:
|
|
17
|
+
self.dir_client = DirectoryClient(DirectoryClientArgs("os_client"))
|
|
18
|
+
|
|
19
|
+
def _setup_io_args(
|
|
20
|
+
self,
|
|
21
|
+
rtsp_user: str,
|
|
22
|
+
rtsp_pwd: str,
|
|
23
|
+
ip: str,
|
|
24
|
+
dest_path: str,
|
|
25
|
+
record_prefix: str,
|
|
26
|
+
record_type: str,
|
|
27
|
+
):
|
|
28
|
+
input_arg = f"rtsp://{rtsp_user}:{rtsp_pwd}@{ip}/stream1"
|
|
29
|
+
output_file = (
|
|
30
|
+
record_prefix
|
|
31
|
+
+ "_"
|
|
32
|
+
+ datetime_to_str(today(), date_format="%m-%d-%Y_%H-%M-%S")
|
|
33
|
+
+ f".{record_type}"
|
|
34
|
+
)
|
|
35
|
+
output_arg = self.dir_client.join_paths(dest_path, output_file)
|
|
36
|
+
return input_arg, output_arg
|
|
37
|
+
|
|
38
|
+
async def _ensure_ffmpeg_available(self):
|
|
39
|
+
if shutil.which("ffmpeg"):
|
|
40
|
+
return
|
|
41
|
+
install_cmd = ["apk", "add", "ffmpeg"] # Only for Home Assistant OS
|
|
42
|
+
process = await asyncio.create_subprocess_shell(
|
|
43
|
+
*install_cmd,
|
|
44
|
+
stdout=asyncio.subprocess.DEVNULL,
|
|
45
|
+
stdin=asyncio.subprocess.PIPE,
|
|
46
|
+
)
|
|
47
|
+
_, stderr = await process.communicate()
|
|
48
|
+
if process.returncode != 0:
|
|
49
|
+
raise ConnectionAbortedError("Failed to install ffmpeg: ", stderr.decode())
|
|
50
|
+
|
|
51
|
+
async def _setup_command(
|
|
52
|
+
self, input_arg: str, output_arg: str, extra: dict
|
|
53
|
+
) -> List[str]:
|
|
54
|
+
await self._ensure_ffmpeg_available() # Validate ffmpeg module is installed.
|
|
55
|
+
cmd = ["ffmpeg", "-y", "-i", input_arg, output_arg]
|
|
56
|
+
for k, v in extra.values():
|
|
57
|
+
cmd.insert(-1, k)
|
|
58
|
+
cmd.insert(-1, v)
|
|
59
|
+
return list(map(str, cmd))
|
|
60
|
+
|
|
61
|
+
async def record_video(self, args: RecordVideoArgs):
|
|
62
|
+
input_arg, output_arg = self._setup_io_args(
|
|
63
|
+
args.rtsp_user,
|
|
64
|
+
args.rtsp_password,
|
|
65
|
+
args.ip_address,
|
|
66
|
+
args.destination_path,
|
|
67
|
+
"Video",
|
|
68
|
+
args.extension,
|
|
69
|
+
)
|
|
70
|
+
cmd = await self._setup_command(
|
|
71
|
+
input_arg, output_arg, extra={"-t": args.duration}
|
|
72
|
+
)
|
|
73
|
+
process = await asyncio.create_subprocess_exec(
|
|
74
|
+
*cmd, stderr=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE
|
|
75
|
+
)
|
|
76
|
+
_, stderr = await process.communicate()
|
|
77
|
+
if process.returncode != 0:
|
|
78
|
+
raise ConnectionError(
|
|
79
|
+
"Unexpected error while recording the video: ", stderr.decode()
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
async def record_photo(self, args: RecordPhotoArgs):
|
|
83
|
+
input_arg, output_arg = self._setup_io_args(
|
|
84
|
+
args.rtsp_user,
|
|
85
|
+
args.rtsp_password,
|
|
86
|
+
args.ip_address,
|
|
87
|
+
args.destination_path,
|
|
88
|
+
"Photo",
|
|
89
|
+
args.extension,
|
|
90
|
+
)
|
|
91
|
+
cmd = await self._setup_command(
|
|
92
|
+
input_arg, output_arg, extra={"-frames:v": args.frames}
|
|
93
|
+
)
|
|
94
|
+
process = await asyncio.create_subprocess_exec(
|
|
95
|
+
*cmd, stderr=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE
|
|
96
|
+
)
|
|
97
|
+
_, stderr = await process.communicate()
|
|
98
|
+
if process.returncode != 0:
|
|
99
|
+
raise ConnectionError(
|
|
100
|
+
"Unexpected error while recording the photo: ", stderr.decode()
|
|
101
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .ffmpeg_client_contract import FfmpegClientContract
|
|
2
|
+
from .types.ffmpeg_client_types import (
|
|
3
|
+
RecordPhotoArgs,
|
|
4
|
+
RecordVideoArgs,
|
|
5
|
+
FfmpegClientInitArgs,
|
|
6
|
+
)
|
|
7
|
+
from .clients.ffmpeg_asyncio_client import FfmpegAsyncioClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FfmpegClient(FfmpegClientContract):
|
|
11
|
+
CLIENTS = {"ffmpeg_asyncio"}
|
|
12
|
+
|
|
13
|
+
def __init__(self, args: FfmpegClientInitArgs) -> None:
|
|
14
|
+
if args.client_name not in FfmpegClient.CLIENTS:
|
|
15
|
+
msg = f"Unsupported client {args.client_name}"
|
|
16
|
+
raise KeyError(msg)
|
|
17
|
+
if args.client_name == "ffmpeg_asyncio":
|
|
18
|
+
self.client_obj = FfmpegAsyncioClient(args)
|
|
19
|
+
self.client_name = args.client_name
|
|
20
|
+
|
|
21
|
+
def record_photo(self, args: RecordPhotoArgs):
|
|
22
|
+
return self.client_obj.record_photo(args)
|
|
23
|
+
|
|
24
|
+
def record_video(self, args: RecordVideoArgs):
|
|
25
|
+
return self.client_obj.record_video(args)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from .types.ffmpeg_client_types import RecordPhotoArgs, RecordVideoArgs
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FfmpegClientContract(ABC):
|
|
6
|
+
@abstractmethod
|
|
7
|
+
async def record_photo(self, args: RecordPhotoArgs):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
async def record_video(self, args: RecordVideoArgs):
|
|
12
|
+
pass
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class FfmpegClientInitArgs:
|
|
6
|
+
client_name: str
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class RecordPhotoArgs:
|
|
11
|
+
ip_address: str
|
|
12
|
+
destination_path: str
|
|
13
|
+
rtsp_user: str
|
|
14
|
+
rtsp_password: str
|
|
15
|
+
port: str
|
|
16
|
+
extension: str
|
|
17
|
+
frames: int
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class RecordVideoArgs:
|
|
22
|
+
destination_path: str
|
|
23
|
+
ip_address: str
|
|
24
|
+
port: str
|
|
25
|
+
rtsp_user: str
|
|
26
|
+
rtsp_password: str
|
|
27
|
+
extension: str
|
|
28
|
+
duration: int
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from saviialib.libs.sftp_client.sftp_client_contract import SFTPClientContract
|
|
2
|
+
from saviialib.libs.sftp_client.types.sftp_client_types import (
|
|
3
|
+
SFTPClientInitArgs,
|
|
4
|
+
ListfilesArgs,
|
|
5
|
+
DownloadfilesArgs,
|
|
6
|
+
)
|
|
7
|
+
from typing import Optional, List, Tuple
|
|
8
|
+
from saviialib.general_types.error_types.common.common_types import EmptyDataError
|
|
9
|
+
import asyncssh # type: ignore
|
|
10
|
+
from saviialib.libs.directory_client import DirectoryClient, DirectoryClientArgs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AsyncsshSFTPClient(SFTPClientContract):
|
|
14
|
+
def __init__(self, args: SFTPClientInitArgs) -> None:
|
|
15
|
+
self.host: str = args.host
|
|
16
|
+
self.port: int = args.port
|
|
17
|
+
self.username: str = args.username
|
|
18
|
+
self.password: Optional[str] = args.password
|
|
19
|
+
self.ssh_key_path: Optional[str] = args.ssh_key_path
|
|
20
|
+
self.dir_client = DirectoryClient(DirectoryClientArgs("os_client"))
|
|
21
|
+
self._validate_credentials()
|
|
22
|
+
|
|
23
|
+
def _validate_credentials(self):
|
|
24
|
+
if not self.password and not self.ssh_key_path:
|
|
25
|
+
raise EmptyDataError(
|
|
26
|
+
reason="At least one attribute (ssh key or password) must be provided"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
async def _start_connection(
|
|
30
|
+
self,
|
|
31
|
+
) -> Tuple[asyncssh.SSHClientConnection, asyncssh.SFTPClient]:
|
|
32
|
+
try:
|
|
33
|
+
ssh_connection = await asyncssh.connect(
|
|
34
|
+
self.host,
|
|
35
|
+
username=self.username,
|
|
36
|
+
port=self.port,
|
|
37
|
+
client_keys=[self.ssh_key_path],
|
|
38
|
+
password=self.password,
|
|
39
|
+
)
|
|
40
|
+
sftp_client = await ssh_connection.start_sftp_client()
|
|
41
|
+
return ssh_connection, sftp_client
|
|
42
|
+
except (OSError, asyncssh.Error) as exc:
|
|
43
|
+
raise ConnectionError("SFTP Operation failed: " + str(exc))
|
|
44
|
+
|
|
45
|
+
async def list_files(self, args: ListfilesArgs) -> List[str]:
|
|
46
|
+
ssh_conn, sftp_client = await self._start_connection()
|
|
47
|
+
async with ssh_conn:
|
|
48
|
+
async with sftp_client:
|
|
49
|
+
files = await sftp_client.listdir(args.path)
|
|
50
|
+
files = [f for f in files if f not in (".", "..")]
|
|
51
|
+
return files
|
|
52
|
+
await self._end_connection(ssh_conn)
|
|
53
|
+
|
|
54
|
+
async def download_files(self, args: DownloadfilesArgs) -> None:
|
|
55
|
+
ssh_conn, sftp_client = await self._start_connection()
|
|
56
|
+
if not args.destination_path or not args.source_path:
|
|
57
|
+
conflict_path = (
|
|
58
|
+
"destination path" if not args.destination_path else "source path"
|
|
59
|
+
)
|
|
60
|
+
raise ConnectionError(f"The {conflict_path} must be provided.")
|
|
61
|
+
|
|
62
|
+
download_all = len(args.files_to_download) == 0
|
|
63
|
+
async with ssh_conn:
|
|
64
|
+
async with sftp_client:
|
|
65
|
+
if download_all:
|
|
66
|
+
await sftp_client.get(
|
|
67
|
+
args.source_path,
|
|
68
|
+
args.destination_path,
|
|
69
|
+
recurse=True,
|
|
70
|
+
preserve=True,
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
for filename in args.files_to_download:
|
|
74
|
+
source_path = self.dir_client.join_paths(
|
|
75
|
+
args.source_path, filename
|
|
76
|
+
)
|
|
77
|
+
dest_path = self.dir_client.join_paths(
|
|
78
|
+
args.destination_path, filename
|
|
79
|
+
)
|
|
80
|
+
await sftp_client.get(source_path, dest_path)
|
|
81
|
+
|
|
82
|
+
async def _end_connection(self, connection) -> None:
|
|
83
|
+
await connection.wait_closed()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from .sftp_client_contract import SFTPClientContract
|
|
2
|
+
from .types.sftp_client_types import (
|
|
3
|
+
SFTPClientInitArgs,
|
|
4
|
+
ListfilesArgs,
|
|
5
|
+
DownloadfilesArgs,
|
|
6
|
+
)
|
|
7
|
+
from .clients.asyncssh_sftp_client import AsyncsshSFTPClient
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SFTPClient(SFTPClientContract):
|
|
12
|
+
CLIENTS = {"asyncssh_sftp"}
|
|
13
|
+
|
|
14
|
+
def __init__(self, args: SFTPClientInitArgs) -> None:
|
|
15
|
+
if args.client_name not in SFTPClient.CLIENTS:
|
|
16
|
+
msg = f"Unsupported client {args.client_name}"
|
|
17
|
+
raise KeyError(msg)
|
|
18
|
+
if args.client_name == "asyncssh_sftp":
|
|
19
|
+
self.client_obj = AsyncsshSFTPClient(args)
|
|
20
|
+
self.client_name = args.client_name
|
|
21
|
+
|
|
22
|
+
async def list_files(self, args: ListfilesArgs) -> List[str]:
|
|
23
|
+
return await self.client_obj.list_files(args)
|
|
24
|
+
|
|
25
|
+
async def download_files(self, args: DownloadfilesArgs) -> None:
|
|
26
|
+
return await self.client_obj.download_files(args)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .types.sftp_client_types import ListfilesArgs, DownloadfilesArgs
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SFTPClientContract(ABC):
|
|
7
|
+
@abstractmethod
|
|
8
|
+
async def list_files(self, args: ListfilesArgs) -> List[str]:
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
async def download_files(self, args: DownloadfilesArgs) -> None:
|
|
13
|
+
pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class SFTPClientInitArgs:
|
|
7
|
+
client_name: str
|
|
8
|
+
password: Optional[str]
|
|
9
|
+
username: str
|
|
10
|
+
ssh_key_path: str
|
|
11
|
+
host: str = "localhost"
|
|
12
|
+
port: int = 22
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ListfilesArgs:
|
|
17
|
+
path: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class DownloadfilesArgs:
|
|
22
|
+
source_path: str
|
|
23
|
+
destination_path: str
|
|
24
|
+
files_to_download: List[str] = field(default_factory=list)
|
|
@@ -19,6 +19,7 @@ from saviialib.libs.sharepoint_client.types.sharepoint_client_types import (
|
|
|
19
19
|
load_dotenv()
|
|
20
20
|
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
|
21
21
|
|
|
22
|
+
|
|
22
23
|
class SharepointRestAPI(SharepointClientContract):
|
|
23
24
|
def __init__(self, args: SharepointClientInitArgs):
|
|
24
25
|
self.session: ClientSession | None = None
|
saviialib/services/backup/api.py
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .controllers.get_media_files import (
|
|
2
|
+
GetMediaFilesController,
|
|
3
|
+
GetMediaFilesControllerInput,
|
|
4
|
+
)
|
|
5
|
+
from typing import Dict, Tuple, Any
|
|
6
|
+
from saviialib.general_types.api.saviia_netcamera_api_types import SaviiaNetcameraConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NetcameraAPI:
|
|
10
|
+
"""This class provides methods for interacting with network cameras and retrieving
|
|
11
|
+
files using streaming services."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, config: SaviiaNetcameraConfig):
|
|
14
|
+
self.config = config
|
|
15
|
+
|
|
16
|
+
async def get_media_files(
|
|
17
|
+
self, cameras: Dict[str, Tuple[str, int]]
|
|
18
|
+
) -> Dict[str, Any]:
|
|
19
|
+
"""Retrieve media files from Network cameras.
|
|
20
|
+
|
|
21
|
+
:param cameras: Dictionary where the key is the identifier of the camera, and the
|
|
22
|
+
value is a tuple wich contains the service IP address and port of connection.
|
|
23
|
+
Example: {'cam_01': ('192.168.1.10', 8080), ...}
|
|
24
|
+
:type cameras: dict
|
|
25
|
+
:return response: A dictionary containg information of the extraction operation.
|
|
26
|
+
:rtype: dict
|
|
27
|
+
"""
|
|
28
|
+
controller = GetMediaFilesController(GetMediaFilesControllerInput(cameras))
|
|
29
|
+
response = await controller.execute()
|
|
30
|
+
return response.__dict__
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .types.get_media_files_types import (
|
|
2
|
+
GetMediaFilesControllerInput,
|
|
3
|
+
GetMediaFilesControllerOutput,
|
|
4
|
+
)
|
|
5
|
+
from saviialib.services.netcamera.use_cases.get_media_files import (
|
|
6
|
+
GetMediaFilesUseCase,
|
|
7
|
+
GetMediaFilesUseCaseInput,
|
|
8
|
+
)
|
|
9
|
+
from http import HTTPStatus
|
|
10
|
+
from saviialib.general_types.error_types.api.saviia_netcamera_error_types import (
|
|
11
|
+
NetcameraConnectionError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GetMediaFilesController:
|
|
16
|
+
def __init__(self, input: GetMediaFilesControllerInput) -> None:
|
|
17
|
+
self.use_case = GetMediaFilesUseCase(
|
|
18
|
+
GetMediaFilesUseCaseInput(
|
|
19
|
+
cameras=input.cameras,
|
|
20
|
+
username=input.config.username,
|
|
21
|
+
password=input.config.password,
|
|
22
|
+
protocol=input.config.protocol,
|
|
23
|
+
logger=input.config.logger,
|
|
24
|
+
destination_path=input.config.destination_path,
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
async def execute(self) -> GetMediaFilesControllerOutput:
|
|
29
|
+
try:
|
|
30
|
+
_ = await self.use_case.execute()
|
|
31
|
+
return GetMediaFilesControllerOutput(
|
|
32
|
+
message="The extraction of media files was successfully!",
|
|
33
|
+
status=HTTPStatus.OK.value,
|
|
34
|
+
)
|
|
35
|
+
except NetcameraConnectionError as error:
|
|
36
|
+
return GetMediaFilesControllerOutput(
|
|
37
|
+
message="An unnexpected error ocurred while extractingphotos and videos.",
|
|
38
|
+
status=HTTPStatus.GATEWAY_TIMEOUT.value,
|
|
39
|
+
metadata={"error": error.__str__()},
|
|
40
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Dict, Tuple
|
|
3
|
+
from saviialib.general_types.api.saviia_netcamera_api_types import SaviiaNetcameraConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class GetMediaFilesControllerInput:
|
|
8
|
+
config = SaviiaNetcameraConfig
|
|
9
|
+
cameras: Dict[str, Tuple[str, int]]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class GetMediaFilesControllerOutput:
|
|
14
|
+
status: int
|
|
15
|
+
message: str
|
|
16
|
+
metadata: Dict = field(default_factory=dict)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from .types.get_media_files_types import (
|
|
2
|
+
GetMediaFilesUseCaseInput,
|
|
3
|
+
GetMediaFilesUseCaseOutput,
|
|
4
|
+
)
|
|
5
|
+
from saviialib.libs.ffmpeg_client import (
|
|
6
|
+
FfmpegClient,
|
|
7
|
+
FfmpegClientInitArgs,
|
|
8
|
+
RecordVideoArgs,
|
|
9
|
+
RecordPhotoArgs,
|
|
10
|
+
)
|
|
11
|
+
from saviialib.libs.zero_dependency.utils.strings_utils import are_equal
|
|
12
|
+
from typing import Tuple, Dict
|
|
13
|
+
from saviialib.libs.directory_client.directory_client import (
|
|
14
|
+
DirectoryClient,
|
|
15
|
+
DirectoryClientArgs,
|
|
16
|
+
)
|
|
17
|
+
from saviialib.general_types.error_types.api.saviia_netcamera_error_types import (
|
|
18
|
+
NetcameraConnectionError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GetMediaFilesUseCase:
|
|
23
|
+
def __init__(self, input: GetMediaFilesUseCaseInput) -> None:
|
|
24
|
+
self.ffmpeg_client = FfmpegClient(
|
|
25
|
+
FfmpegClientInitArgs(
|
|
26
|
+
client_name="ffmpeg_asyncio",
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
self.dir_client = DirectoryClient(DirectoryClientArgs("os_client"))
|
|
30
|
+
self.user = input.username
|
|
31
|
+
self.pwd = input.password
|
|
32
|
+
self.cameras: Dict[str, Tuple[str, int]] = input.cameras
|
|
33
|
+
self.protocol = input.protocol
|
|
34
|
+
self.logger = input.logger
|
|
35
|
+
self.dest_path = input.destination_path
|
|
36
|
+
|
|
37
|
+
async def _retieve_with_rtsp(self):
|
|
38
|
+
for name, conn in self.cameras.items():
|
|
39
|
+
ip, port = conn
|
|
40
|
+
dest_path = self.dir_client.join_paths(self.dest_path, name)
|
|
41
|
+
try:
|
|
42
|
+
# Extraction of photo files into dest_path dir.
|
|
43
|
+
await self.ffmpeg_client.record_photo(
|
|
44
|
+
RecordPhotoArgs(
|
|
45
|
+
ip_address=ip,
|
|
46
|
+
port=str(port),
|
|
47
|
+
destination_path=dest_path,
|
|
48
|
+
rtsp_user=self.user,
|
|
49
|
+
rtsp_password=self.pwd,
|
|
50
|
+
extension="jpg",
|
|
51
|
+
frames=1,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
# Extraction of video files into dest_path dir.
|
|
55
|
+
await self.ffmpeg_client.record_video(
|
|
56
|
+
RecordVideoArgs(
|
|
57
|
+
destination_path=dest_path,
|
|
58
|
+
ip_address=ip,
|
|
59
|
+
port=str(port),
|
|
60
|
+
rtsp_user=self.user,
|
|
61
|
+
rtsp_password=self.pwd,
|
|
62
|
+
extension="mp3",
|
|
63
|
+
duration=10,
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
except ConnectionError as error:
|
|
67
|
+
raise NetcameraConnectionError(reason=error)
|
|
68
|
+
|
|
69
|
+
async def execute(self) -> GetMediaFilesUseCaseOutput:
|
|
70
|
+
if are_equal(self.protocol, "rtsp"):
|
|
71
|
+
await self._retieve_with_rtsp()
|
|
72
|
+
else:
|
|
73
|
+
raise NotImplementedError(
|
|
74
|
+
f"The media files extraction with {self.protocol} is not implemented yet."
|
|
75
|
+
)
|
|
76
|
+
return GetMediaFilesUseCaseOutput()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from logging import Logger
|
|
3
|
+
from typing import Dict, Tuple
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class GetMediaFilesUseCaseInput:
|
|
8
|
+
cameras: Dict[str, Tuple[str, int]]
|
|
9
|
+
username: str
|
|
10
|
+
password: str
|
|
11
|
+
protocol: str
|
|
12
|
+
logger: Logger
|
|
13
|
+
destination_path: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class GetMediaFilesUseCaseOutput:
|
|
18
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from .controllers.get_miniseed_files import (
|
|
2
|
+
GetMiniseedFilesController,
|
|
3
|
+
GetMiniseedFilesControllerInput,
|
|
4
|
+
)
|
|
5
|
+
from saviialib.general_types.api.saviia_shakes_api_types import SaviiaShakesConfig
|
|
6
|
+
|
|
7
|
+
from typing import Dict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ShakesAPI:
|
|
11
|
+
"""This class provides methods for interacting with Raspberry Shakes"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, config: SaviiaShakesConfig) -> None:
|
|
14
|
+
self.config = config
|
|
15
|
+
|
|
16
|
+
async def get_miniseed_files(self, raspberry_shakes: Dict[str, str]):
|
|
17
|
+
"""Download the MiniSEED files from the SFTP Server provided by each Raspberry Shake.
|
|
18
|
+
Args:
|
|
19
|
+
raspberry_shakes (dict): Dictionary where the key is the name of the Raspberry Shake,
|
|
20
|
+
and the value is the IP Address.
|
|
21
|
+
Returns:
|
|
22
|
+
response (dict): A dictionary containg the response from the download operation.
|
|
23
|
+
This response will tipically include the message, the response status, and metadata.
|
|
24
|
+
"""
|
|
25
|
+
controller = GetMiniseedFilesController(
|
|
26
|
+
GetMiniseedFilesControllerInput(
|
|
27
|
+
config=self.config, raspberry_shakes=raspberry_shakes
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
response = await controller.execute()
|
|
31
|
+
return response.__dict__
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from .types.get_miniseed_files_types import (
|
|
2
|
+
GetMiniseedFilesControllerInput,
|
|
3
|
+
GetMiniseedFilesControllerOutput,
|
|
4
|
+
)
|
|
5
|
+
from saviialib.services.shakes.use_cases.get_miniseed_files import (
|
|
6
|
+
GetMiniseedFilesUseCase,
|
|
7
|
+
GetMiniseedFilesUseCaseInput,
|
|
8
|
+
)
|
|
9
|
+
from http import HTTPStatus
|
|
10
|
+
from saviialib.general_types.error_types.api.saviia_api_error_types import (
|
|
11
|
+
ShakesNoContentError,
|
|
12
|
+
)
|
|
13
|
+
from saviialib.general_types.error_types.common.common_types import SftpClientError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GetMiniseedFilesController:
|
|
17
|
+
def __init__(self, input: GetMiniseedFilesControllerInput) -> None:
|
|
18
|
+
self.use_case = GetMiniseedFilesUseCase(
|
|
19
|
+
GetMiniseedFilesUseCaseInput(
|
|
20
|
+
raspberry_shakes=input.raspberry_shakes,
|
|
21
|
+
username=input.config.sftp_user,
|
|
22
|
+
password=input.config.sftp_password,
|
|
23
|
+
ssh_key_path=input.config.ssh_key_path,
|
|
24
|
+
port=input.config.sftp_port,
|
|
25
|
+
logger=input.config.logger,
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
async def execute(self) -> GetMiniseedFilesControllerOutput:
|
|
30
|
+
try:
|
|
31
|
+
res = await self.use_case.execute()
|
|
32
|
+
return GetMiniseedFilesControllerOutput(
|
|
33
|
+
message="The MiniSEED files have been downloaded succesfully!",
|
|
34
|
+
status=HTTPStatus.OK.value,
|
|
35
|
+
metadata=res.download_status,
|
|
36
|
+
)
|
|
37
|
+
except ShakesNoContentError as error:
|
|
38
|
+
return GetMiniseedFilesControllerOutput(
|
|
39
|
+
message="No files to upload.",
|
|
40
|
+
status=HTTPStatus.NO_CONTENT.value,
|
|
41
|
+
metadata={"error": error.__str__()},
|
|
42
|
+
)
|
|
43
|
+
except SftpClientError as error:
|
|
44
|
+
return GetMiniseedFilesControllerOutput(
|
|
45
|
+
message="An unexpected error ocurred during SFTP Client connection.",
|
|
46
|
+
status=HTTPStatus.REQUEST_TIMEOUT.value,
|
|
47
|
+
metadata={"error": error.__str__()},
|
|
48
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict
|
|
3
|
+
from saviialib.general_types.api.saviia_shakes_api_types import SaviiaShakesConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class GetMiniseedFilesControllerInput:
|
|
8
|
+
raspberry_shakes: Dict[str, str]
|
|
9
|
+
config: SaviiaShakesConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class GetMiniseedFilesControllerOutput:
|
|
14
|
+
status: int
|
|
15
|
+
metadata: Dict
|
|
16
|
+
message: str
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from .types.get_miniseed_files_types import (
|
|
3
|
+
GetMiniseedFilesUseCaseInput,
|
|
4
|
+
GetMiniseedFilesUseCaseOutput,
|
|
5
|
+
)
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
from saviialib.libs.sftp_client import (
|
|
8
|
+
SFTPClient,
|
|
9
|
+
SFTPClientInitArgs,
|
|
10
|
+
ListfilesArgs,
|
|
11
|
+
DownloadfilesArgs,
|
|
12
|
+
)
|
|
13
|
+
from saviialib.libs.directory_client import DirectoryClient, DirectoryClientArgs
|
|
14
|
+
from saviialib.general_types.error_types.api.saviia_api_error_types import (
|
|
15
|
+
ShakesNoContentError,
|
|
16
|
+
)
|
|
17
|
+
from saviialib.general_types.error_types.common.common_types import SftpClientError
|
|
18
|
+
from .utils.get_miniseed_files_utils import parse_downloaded_metadata
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GetMiniseedFilesUseCase:
|
|
22
|
+
def __init__(self, input: GetMiniseedFilesUseCaseInput) -> None:
|
|
23
|
+
self.password = input.password
|
|
24
|
+
self.username = input.username
|
|
25
|
+
self.ssh_key_path = input.ssh_key_path
|
|
26
|
+
self.port = input.port
|
|
27
|
+
self.raspberry_shakes: Dict[str, str] = input.raspberry_shakes
|
|
28
|
+
self.dir_client = DirectoryClient(DirectoryClientArgs("os_client"))
|
|
29
|
+
|
|
30
|
+
def _initialize_sftp_client(self, ip_address: str):
|
|
31
|
+
return SFTPClient(
|
|
32
|
+
SFTPClientInitArgs(
|
|
33
|
+
"asyncssh_sftp",
|
|
34
|
+
password=self.password,
|
|
35
|
+
username=self.username,
|
|
36
|
+
ssh_key_path=self.ssh_key_path,
|
|
37
|
+
host=ip_address,
|
|
38
|
+
port=self.port,
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
async def _download_mseed_file(self, rs_name: str, rs_ip: str) -> Dict[str, Any]:
|
|
43
|
+
DEST_BASE_DIR = "./rshakes-mseed-files"
|
|
44
|
+
SOURCE_PATH = self.dir_client.join_paths("opt", "data", "archive")
|
|
45
|
+
local_path = self.dir_client.join_paths(DEST_BASE_DIR, rs_name)
|
|
46
|
+
if not await self.dir_client.isdir(local_path):
|
|
47
|
+
await self.dir_client.makedirs(local_path)
|
|
48
|
+
sftp_client = self._initialize_sftp_client(rs_ip)
|
|
49
|
+
|
|
50
|
+
local_files = await self.dir_client.listdir(local_path)
|
|
51
|
+
try:
|
|
52
|
+
sftp_files = await sftp_client.list_files(ListfilesArgs(path=SOURCE_PATH))
|
|
53
|
+
pending_files = set(sftp_files) - set(local_files)
|
|
54
|
+
if not pending_files:
|
|
55
|
+
raise ShakesNoContentError
|
|
56
|
+
|
|
57
|
+
await sftp_client.download_files(
|
|
58
|
+
DownloadfilesArgs(
|
|
59
|
+
source_path=SOURCE_PATH,
|
|
60
|
+
destination_path=local_path,
|
|
61
|
+
files_to_download=list(pending_files),
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
except ConnectionError as error:
|
|
65
|
+
raise SftpClientError(reason=error)
|
|
66
|
+
return {
|
|
67
|
+
"rs_name": rs_name,
|
|
68
|
+
"destination_path": local_path,
|
|
69
|
+
"total_files": len(pending_files),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async def execute(self):
|
|
73
|
+
requests = []
|
|
74
|
+
for rs_name, rs_ip in self.raspberry_shakes:
|
|
75
|
+
requests.append(self._download_mseed_file(rs_name, rs_ip))
|
|
76
|
+
responses = await asyncio.gather(*requests, return_exceptions=True)
|
|
77
|
+
return GetMiniseedFilesUseCaseOutput(
|
|
78
|
+
download_status=parse_downloaded_metadata(responses),
|
|
79
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Dict
|
|
3
|
+
from logging import Logger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class GetMiniseedFilesUseCaseInput:
|
|
8
|
+
raspberry_shakes: Dict[str, str]
|
|
9
|
+
username: str
|
|
10
|
+
password: str
|
|
11
|
+
ssh_key_path: str
|
|
12
|
+
port: int
|
|
13
|
+
logger: Logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class GetMiniseedFilesUseCaseOutput:
|
|
18
|
+
download_status: Dict[str, str] = field(default_factory=dict)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from .thies_bp import THIESDayData
|
|
2
|
-
from typing import List
|
|
3
2
|
from logging import Logger
|
|
4
3
|
from asyncio import to_thread
|
|
5
4
|
from saviialib.libs.directory_client import DirectoryClient
|
|
@@ -134,7 +133,8 @@ async def create_thies_daily_statistics_file(
|
|
|
134
133
|
)
|
|
135
134
|
mean = (min_val + max_val) / 2
|
|
136
135
|
logger.warning(
|
|
137
|
-
f"[thies_synchronization_lib] Mean value corrected to {mean}."
|
|
136
|
+
f"[thies_synchronization_lib] Mean value corrected to {mean}."
|
|
137
|
+
)
|
|
138
138
|
|
|
139
139
|
if col in ["WD"]: # Avoid error
|
|
140
140
|
rows.append(
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
from typing import Dict, List
|
|
3
|
-
from saviialib.general_types.api.saviia_api_types import
|
|
3
|
+
from saviialib.general_types.api.saviia_api_types import (
|
|
4
|
+
FtpClientConfig,
|
|
5
|
+
SharepointConfig,
|
|
6
|
+
)
|
|
4
7
|
from logging import Logger
|
|
5
8
|
|
|
6
9
|
|
|
@@ -67,7 +67,7 @@ class UpdateThiesDataUseCase:
|
|
|
67
67
|
def _initialize_thies_ftp_client(self, config: FtpClientConfig) -> FTPClient:
|
|
68
68
|
"""Initialize the FTP client."""
|
|
69
69
|
try:
|
|
70
|
-
return FTPClient(FtpClientInitArgs(config, client_name="
|
|
70
|
+
return FTPClient(FtpClientInitArgs(config, client_name="ftplib_client"))
|
|
71
71
|
except RuntimeError as error:
|
|
72
72
|
raise FtpClientError(error)
|
|
73
73
|
|
|
@@ -270,10 +270,10 @@ class UpdateThiesDataUseCase:
|
|
|
270
270
|
FtpReadFileArgs(file_path)
|
|
271
271
|
)
|
|
272
272
|
except FileNotFoundError as error:
|
|
273
|
-
reason =
|
|
274
|
-
|
|
275
|
-
"[thies_synchronization_lib] Warning: %s", reason
|
|
273
|
+
reason = (
|
|
274
|
+
str(error) + ". The file might not be available yet for statistics."
|
|
276
275
|
)
|
|
276
|
+
self.logger.warning("[thies_synchronization_lib] Warning: %s", reason)
|
|
277
277
|
self.logger.warning(
|
|
278
278
|
"[thies_synchronization_lib] Skipping the creation of daily statistics %s",
|
|
279
279
|
filename,
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: saviialib
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: A client library for IoT projects in the RCER initiative
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: pedropablozavalat
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: aiofiles
|
|
16
|
+
Requires-Dist: aioftp
|
|
17
|
+
Requires-Dist: aiohttp
|
|
18
|
+
Requires-Dist: asyncssh (==2.21.1)
|
|
19
|
+
Requires-Dist: bitarray
|
|
20
|
+
Requires-Dist: build
|
|
21
|
+
Requires-Dist: certifi
|
|
22
|
+
Requires-Dist: dotenv (>=0.9.9,<0.10.0)
|
|
23
|
+
Requires-Dist: ffmpeg-asyncio (==0.1.3)
|
|
24
|
+
Requires-Dist: numpy (>=2.2.0,<2.4.0)
|
|
25
|
+
Requires-Dist: pandas (>=2.2.3,<2.3.1)
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# SAVIIA Library
|
|
29
|
+
*Sistema de Administración y Visualización de Información para la Investigación y Análisis*
|
|
30
|
+
|
|
31
|
+
[](https://github.com/pedrozavalat/saviia-lib/releases)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
This library is designed for use with the SAVIIA Home Assistant Integration. It provides an API to retrieve files from a THIES Data Logger via an FTP server and upload them to a Microsoft SharePoint folder using the SharePoint REST API.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install saviialib
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Saviia API Client Usage
|
|
42
|
+
|
|
43
|
+
### Initialize the Saviia API Client
|
|
44
|
+
Import the necessary classes from the library.
|
|
45
|
+
```python
|
|
46
|
+
from saviialib import SaviiaAPI, SaviiaAPIConfig
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
To start using the library, you need to create an `SaviiaAPI` client instance with its configuration class `SaviiaAPIConfig`. Provide the required parameters such as FTP server details and SharePoint credentials:
|
|
50
|
+
```python
|
|
51
|
+
config = SaviiaAPIConfig(
|
|
52
|
+
ftp_port=FTP_PORT,
|
|
53
|
+
ftp_host=FTP_HOST,
|
|
54
|
+
ftp_user=FTP_USER,
|
|
55
|
+
ftp_password=FTP_PASSWORD,
|
|
56
|
+
sharepoint_client_id=SHAREPOINT_CLIENT_ID,
|
|
57
|
+
sharepoint_client_secret=SHAREPOINT_CLIENT_SECRET,
|
|
58
|
+
sharepoint_tenant_id=SHAREPOINT_TENANT_ID,
|
|
59
|
+
sharepoint_tenant_name=SHAREPOINT_TENANT_NAME,
|
|
60
|
+
sharepoint_site_name=SHAREPOINT_SITE_NAME
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
```python
|
|
64
|
+
api_client = SaviiaAPI(config)
|
|
65
|
+
```
|
|
66
|
+
**Notes:**
|
|
67
|
+
- 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.
|
|
68
|
+
|
|
69
|
+
### Access THIES Data Logger Services
|
|
70
|
+
To interact with the THIES Data Logger services, you can access the `thies` attribute of the `SaviiaAPI` instance:
|
|
71
|
+
```python
|
|
72
|
+
thies_client = api_client.get('thies')
|
|
73
|
+
```
|
|
74
|
+
This instance provides methods to interact with the THIES Data Logger. Currently, it includes the main method for extracting files from the FTP server and uploading them to SharePoint.
|
|
75
|
+
|
|
76
|
+
#### THIES files extraction and synchronization
|
|
77
|
+
The library provides a method to extract and synchronize THIES Data Logger files with the Microsoft SharePoint client. This method downloads files from the FTP server and uploads them to the specified SharePoint folder:
|
|
78
|
+
```python
|
|
79
|
+
import asyncio
|
|
80
|
+
async def main():
|
|
81
|
+
# Before calling this method, you must have initialised the THIES service class ...
|
|
82
|
+
response = await thies_client.update_thies_data()
|
|
83
|
+
return response
|
|
84
|
+
|
|
85
|
+
asyncio.run(main())
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Access Backup Services
|
|
89
|
+
To interact with the Backup services, you can access the `backup` attribute of the `SaviiaAPI` instance:
|
|
90
|
+
```python
|
|
91
|
+
backup_client = api_client.get('backup')
|
|
92
|
+
```
|
|
93
|
+
This instance provides methods to interact with the Backup services. Currently, it includes the main method for creating backups of specified directories in a local folder from Home Assistant environment. Then each backup file is uploaded to a Microsoft SharePoint folder.
|
|
94
|
+
|
|
95
|
+
#### Create Backup
|
|
96
|
+
The library provides a method which creates a backup of a specified directory in a local folder from Home Assistant environment. Then each backup file is uploaded to a Microsoft SharePoint folder:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import asyncio
|
|
100
|
+
async def main():
|
|
101
|
+
# Before calling this method, you must have initialised the Backup service class ...
|
|
102
|
+
response = await backup_client.upload_backup_to_sharepoint(
|
|
103
|
+
local_backup_path=LOCAL_BACKUP_PATH,
|
|
104
|
+
sharepoint_folder_path=SHAREPOINT_FOLDER_PATH
|
|
105
|
+
)
|
|
106
|
+
return response
|
|
107
|
+
asyncio.run(main())
|
|
108
|
+
```
|
|
109
|
+
**Notes:**
|
|
110
|
+
- Ensure that the `local_backup_path` exists and contains the files you want to back up. It is a relative path from the Home Assistant configuration directory.
|
|
111
|
+
- The `sharepoint_folder_path` should be the path to the folder in SharePoint where you want to upload the backup files. For example, if your url is `https://yourtenant.sharepoint.com/sites/yoursite/Shared Documents/Backups`, the folder path would be `sites/yoursite/Shared Documents/Backups`.
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
## Contributing
|
|
120
|
+
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.
|
|
121
|
+
Contributions are welcome and appreciated!
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
`saviialib` was created by Pedro Pablo Zavala Tejos. It is licensed under the terms of the MIT license.
|
|
126
|
+
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
saviialib/__init__.py,sha256=
|
|
1
|
+
saviialib/__init__.py,sha256=pn4KYFwPsVe43mw1e0svWiqA0StzM2B5Cr0iPc481I0,3074
|
|
2
2
|
saviialib/general_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
saviialib/general_types/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
saviialib/general_types/api/saviia_api_types.py,sha256=
|
|
4
|
+
saviialib/general_types/api/saviia_api_types.py,sha256=kX45V3KQb48S1jEstXcAtbiemVUgemJE_XK5qP7BJm0,1318
|
|
5
5
|
saviialib/general_types/api/saviia_backup_api_types.py,sha256=mtXRxFjBOqfUBWWtgLZxNq-vb_mBkCUS9fxoFRJfoYE,791
|
|
6
|
+
saviialib/general_types/api/saviia_netcamera_api_types.py,sha256=WVA3VP_FiTGPhtTl8GCG2RAVo7YDS5TTyl-0I5rUK4Y,208
|
|
7
|
+
saviialib/general_types/api/saviia_shakes_api_types.py,sha256=XlLwfCyFeQXk9kH7H0EcUJHIigj9edFKgn3wKGJ1Z94,623
|
|
6
8
|
saviialib/general_types/api/saviia_thies_api_types.py,sha256=ueqOFjHvRuT-N8H1UZEVXErgK1S1iRw0AXeO8bTdBts,1017
|
|
7
9
|
saviialib/general_types/error_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
10
|
saviialib/general_types/error_types/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
saviialib/general_types/error_types/api/saviia_api_error_types.py,sha256=
|
|
11
|
+
saviialib/general_types/error_types/api/saviia_api_error_types.py,sha256=BcDhteS4KsFNCm3L9ZQJTQKs0bKRXq3LgM4mfxsW-HY,3270
|
|
12
|
+
saviialib/general_types/error_types/api/saviia_netcamera_error_types.py,sha256=ipdY8PDJOFLxjgUIWc09cuaiHzPtBvAb7YKoosTL0wY,246
|
|
10
13
|
saviialib/general_types/error_types/common/__init__.py,sha256=yOBLZbt64Ki9Q0IJ0tMAubgq7PtrQ7XQ3RgtAzyOjiE,170
|
|
11
|
-
saviialib/general_types/error_types/common/common_types.py,sha256=
|
|
14
|
+
saviialib/general_types/error_types/common/common_types.py,sha256=2w790TQNRLc4UskXNHAh8RMZIJLA8FYyQbVxkPp89UA,717
|
|
12
15
|
saviialib/libs/directory_client/__init__.py,sha256=ys07nzp74fHew2mUkbGpntp5w4t_PnhZIS6D4_mJw2A,162
|
|
13
|
-
saviialib/libs/directory_client/client/os_client.py,sha256=
|
|
16
|
+
saviialib/libs/directory_client/client/os_client.py,sha256=mBIQc8ek8mDY6GMCX-cPNLleM4RbFdqoiuw5Ccoe3YA,763
|
|
14
17
|
saviialib/libs/directory_client/directory_client.py,sha256=5HmB1Pt9M8Tk18WKzfh3Fwr8gKa_RZCdAqvbU0rg7mw,1086
|
|
15
18
|
saviialib/libs/directory_client/directory_client_contract.py,sha256=wtNaUak1a7r6t9OI1L2ou7XMMJFXWpQFb7WyT6X7fCQ,479
|
|
16
19
|
saviialib/libs/directory_client/types/directory_client_types.py,sha256=ncMwVs_o6EYMuypXXmVInsjVDKJsdxVkmwj1M-LEInA,109
|
|
20
|
+
saviialib/libs/ffmpeg_client/__init__.py,sha256=KjOP--Uv0Z-qvoJI9IwwBrU3ZrroaC55zDG7fm9DEts,207
|
|
21
|
+
saviialib/libs/ffmpeg_client/clients/ffmpeg_asyncio_client.py,sha256=eqm7sNm4BY3WaxobXbeoS39DfzfwtICgmQAm9UE7yZw,3639
|
|
22
|
+
saviialib/libs/ffmpeg_client/ffmpeg_client.py,sha256=evqhjQeT9VejtJ8Gb7A3Vjxoj0PT4n7JnbbsuO7-35U,870
|
|
23
|
+
saviialib/libs/ffmpeg_client/ffmpeg_client_contract.py,sha256=mvGrGYkikExz4VyAErcSu6Sd2utYYm9bqxJj1N22z2U,324
|
|
24
|
+
saviialib/libs/ffmpeg_client/types/ffmpeg_client_types.py,sha256=Vo062LHqfTLgRbwLloZMRkC36CO3r9tFqY97nVxr8vE,444
|
|
17
25
|
saviialib/libs/files_client/__init__.py,sha256=sIi9ne7Z3EfxnqGTaSmH-cZ8QsKyu0hoOz61GyA3njs,192
|
|
18
26
|
saviialib/libs/files_client/clients/aiofiles_client.py,sha256=Mu5pSnnEa3dT3GONmt1O-lCQstuQNXHtJHM3D2L6TU8,1107
|
|
19
27
|
saviialib/libs/files_client/clients/csv_client.py,sha256=Rk4QbiKlVKrYxYtxQt-Pmkp9QoB_dNgs5r36JB9hU0o,1478
|
|
@@ -28,14 +36,20 @@ saviialib/libs/ftp_client/ftp_client.py,sha256=Naj9p0yYWlXt9um0zKfvuHSoycM8JZg2S
|
|
|
28
36
|
saviialib/libs/ftp_client/ftp_client_contract.py,sha256=tymkugDzsJ5PzUXIaSkwX1h7T0naR15qAkjrqswqDyM,338
|
|
29
37
|
saviialib/libs/ftp_client/types/__init__.py,sha256=syfwf9feP4QK7fkCTfl4j8l11ic-jHtfi1DE2chaWbs,155
|
|
30
38
|
saviialib/libs/ftp_client/types/ftp_client_types.py,sha256=e4SmYkewldulaD8ms2q75zVgLFXyBxBqoa_L-IQOmso,256
|
|
39
|
+
saviialib/libs/sftp_client/__init__.py,sha256=f51sFOd_XIhhd6x6AZ3ZdbJ86hO1xnqAk9MmEjwRJ-c,229
|
|
40
|
+
saviialib/libs/sftp_client/clients/asyncssh_sftp_client.py,sha256=vqCKHIx3K6QFujQP5DIAB5EYOz1zAdhi825LTRvhG20,3469
|
|
41
|
+
saviialib/libs/sftp_client/sftp_client.py,sha256=_5HRYSBPadxZEdHuHSpQ_VrwerBfu24izNRg-pvLzlU,918
|
|
42
|
+
saviialib/libs/sftp_client/sftp_client_contract.py,sha256=JWWxIfNBXcsBd8z3gQHGpeRASRjtP9WGkznoMfP0IZc,365
|
|
43
|
+
saviialib/libs/sftp_client/types/sftp_client_types.py,sha256=NzUZVq6yeGKkO7KF-v4Cc-rwaWeOgFCUGVPL1YBbYgA,446
|
|
31
44
|
saviialib/libs/sharepoint_client/__init__.py,sha256=6RbEzFtCfMrrY8F0JOCVcK6Gqt1FBJ09m0NuWHMCmCI,384
|
|
32
|
-
saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py,sha256=
|
|
45
|
+
saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py,sha256=I3OfZ5M4uR86hLIBOlOQzGNyTzQRy1yckrLF1ne7F1g,6319
|
|
33
46
|
saviialib/libs/sharepoint_client/sharepoint_client.py,sha256=gXFisWWCk6pKhoEeaNN8dcHA5ywrGDLqfiKjchrHRy0,1816
|
|
34
47
|
saviialib/libs/sharepoint_client/sharepoint_client_contract.py,sha256=H-WsXR5f50jy5RRYNlgV61y_YGjOUq4U_E1DWUUrDYw,612
|
|
35
48
|
saviialib/libs/sharepoint_client/types/sharepoint_client_types.py,sha256=JruUCn6o16w00t4zxDrxmv_Bxa52UyShGYmwUFfBCCQ,482
|
|
36
49
|
saviialib/libs/zero_dependency/utils/datetime_utils.py,sha256=c2H_JpUKpunCzDw6I4alDWNftEqaciWGtEUklPepm_s,759
|
|
50
|
+
saviialib/libs/zero_dependency/utils/strings_utils.py,sha256=XqEukvCLkQ0PhjjPm9FTjrzbwBYMejs-0uSoLwSxhPo,133
|
|
37
51
|
saviialib/services/backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
saviialib/services/backup/api.py,sha256=
|
|
52
|
+
saviialib/services/backup/api.py,sha256=S1dMGyyjYzVyo-E8FMttHZMOO2TtQoAMaLp-oquW46g,1408
|
|
39
53
|
saviialib/services/backup/controllers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
54
|
saviialib/services/backup/controllers/types/__init__.py,sha256=0zIWXQb6YQVIIbb5b_FeL-D9Zy_d1GqR8kVz3hen4IU,222
|
|
41
55
|
saviialib/services/backup/controllers/types/upload_backup_to_sharepoint_types.py,sha256=AzoHx3AWrVCM8LjmlEovDlRe4kJPJBbRoMToG8QK_Nw,464
|
|
@@ -46,16 +60,28 @@ saviialib/services/backup/use_cases/types/upload_backup_to_sharepoint_types.py,s
|
|
|
46
60
|
saviialib/services/backup/use_cases/upload_backup_to_sharepoint.py,sha256=tBAfztcXdr3pmJqhwdHKc6AbddajZubs_wkL93Wajv4,11890
|
|
47
61
|
saviialib/services/backup/utils/__init__.py,sha256=Khz07TAC17xGBbkSgNcOXSjzeFm6-NiQadNJ3R7tRgg,112
|
|
48
62
|
saviialib/services/backup/utils/upload_backup_to_sharepoint_utils.py,sha256=J2PkLvNFHqka4NLW4lrAdaK5pRgHnU3ZSOflO43uu78,3530
|
|
63
|
+
saviialib/services/netcamera/api.py,sha256=W8ZKfh_iGEvfE36yGXYH9RlveUUtHoxQI_E3__YGfq0,1182
|
|
64
|
+
saviialib/services/netcamera/controllers/get_media_files.py,sha256=CEfZ7YRhXBcuGeK3HWB3Bpp-i-pIvAs70krreCHL_eA,1507
|
|
65
|
+
saviialib/services/netcamera/controllers/types/get_media_files_types.py,sha256=40V0ARqtloRM16KYYxzeq5aQU-mrghEK7FfkeFoSmbk,417
|
|
66
|
+
saviialib/services/netcamera/use_cases/get_media_files.py,sha256=ct_MOOgxzqCURQo2JJx1ioBaO-Qq-Gj-27Y4JAdZbeo,2801
|
|
67
|
+
saviialib/services/netcamera/use_cases/types/get_media_files_types.py,sha256=XCpodI-WdZYbhd3DD1LkKDgLTSRKlXkHClbhwT8zXm8,333
|
|
68
|
+
saviialib/services/shakes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
|
+
saviialib/services/shakes/api.py,sha256=WvmOJ35lPdJuqSEl9-q9VOGNxqkqvzfBUmkp-G8WKj4,1230
|
|
70
|
+
saviialib/services/shakes/controllers/get_miniseed_files.py,sha256=k55QiIQoNHyMQsgW939IbrPY4tHy8cFMg51LDVQQSgs,1934
|
|
71
|
+
saviialib/services/shakes/controllers/types/get_miniseed_files_types.py,sha256=LZ8LZxpjPYrl-EIuwQixXl8z4Ygl81DgodoHyxzvIxY,366
|
|
72
|
+
saviialib/services/shakes/use_cases/get_miniseed_files.py,sha256=HD6VphToOF8eq-J281qOg9AgeD6hFAsVfScwvP5c9jc,3033
|
|
73
|
+
saviialib/services/shakes/use_cases/types/get_miniseed_files_types.py,sha256=bb5XurP08pHhhNK80dvgoSP_OmHFEdKhT-jzyd8w5kk,385
|
|
74
|
+
saviialib/services/shakes/use_cases/utils/get_miniseed_files_utils.py,sha256=6TEdu9MglnM0_FTuxs9BETneXqQBUGb-Q1rDKhvRLvg,288
|
|
49
75
|
saviialib/services/thies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
76
|
saviialib/services/thies/api.py,sha256=ibXBSzs_LJjZbC9LWh1EcHMWUp5qWTz-P_qOIb3rx6Y,1365
|
|
51
77
|
saviialib/services/thies/controllers/types/update_thies_data_types.py,sha256=YSLd0HgkeB7auTXFjZVYz_g9jv2hY2vaK7x6VLZ2BK4,439
|
|
52
78
|
saviialib/services/thies/controllers/update_thies_data.py,sha256=j_orYJfom4lo8xm8Jsru1h8JjDFI4xAfUL2oSM_XpcE,4890
|
|
53
|
-
saviialib/services/thies/use_cases/components/create_thies_statistics_file.py,sha256=
|
|
79
|
+
saviialib/services/thies/use_cases/components/create_thies_statistics_file.py,sha256=_1wY_yI_Lg2QTOGag4lVZBWM3B1gp2Duz7ore-kOHhQ,5575
|
|
54
80
|
saviialib/services/thies/use_cases/components/thies_bp.py,sha256=1Iq5Wz3kqzJfQNawV_v8ORr_Pl-PoamFp_2Xo8DjvmI,15042
|
|
55
|
-
saviialib/services/thies/use_cases/types/update_thies_data_types.py,sha256=
|
|
56
|
-
saviialib/services/thies/use_cases/update_thies_data.py,sha256=
|
|
81
|
+
saviialib/services/thies/use_cases/types/update_thies_data_types.py,sha256=S9_oS4Laygh_XIQoKWzaLd9QrAjVajyHcgJgbzEFPww,554
|
|
82
|
+
saviialib/services/thies/use_cases/update_thies_data.py,sha256=6l3AvFRwDMTCChJfMR_f1Y6ejDNiy9eKlD1mfxqFjPw,14962
|
|
57
83
|
saviialib/services/thies/utils/update_thies_data_utils.py,sha256=EpjYWXqyHxJ-dO3MHhdXp-rGV7WyUckeFko-nnfnNac,555
|
|
58
|
-
saviialib-1.
|
|
59
|
-
saviialib-1.
|
|
60
|
-
saviialib-1.
|
|
61
|
-
saviialib-1.
|
|
84
|
+
saviialib-1.5.0.dist-info/METADATA,sha256=gp3HoVlafLCIXXrjex5UmaJHV-2a8uBYpap7EzpkAEk,5235
|
|
85
|
+
saviialib-1.5.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
86
|
+
saviialib-1.5.0.dist-info/licenses/LICENSE,sha256=NWpf6b38xgBWPBo5HZsCbdfp9hZSliEbRqWQgm0fkOo,1076
|
|
87
|
+
saviialib-1.5.0.dist-info/RECORD,,
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: saviialib
|
|
3
|
-
Version: 1.4.0
|
|
4
|
-
Summary: A client library for IoT projects in the RCER initiative
|
|
5
|
-
License: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Author: pedropablozavalat
|
|
8
|
-
Requires-Python: >=3.11,<4.0
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
-
Requires-Dist: aiofiles
|
|
16
|
-
Requires-Dist: aioftp
|
|
17
|
-
Requires-Dist: aiohttp
|
|
18
|
-
Requires-Dist: bitarray
|
|
19
|
-
Requires-Dist: build
|
|
20
|
-
Requires-Dist: certifi
|
|
21
|
-
Requires-Dist: dotenv (>=0.9.9,<0.10.0)
|
|
22
|
-
Requires-Dist: numpy (>=2.2.0,<2.4.0)
|
|
23
|
-
Requires-Dist: pandas (>=2.2.3,<2.3.1)
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
|
|
26
|
-
# SAVIIA Library
|
|
27
|
-
*Sistema de Administración y Visualización de Información para la Investigación y Análisis*
|
|
28
|
-
|
|
29
|
-
[](https://github.com/pedrozavalat/saviia-lib/releases)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
## Installation
|
|
33
|
-
This library is designed for use with the SAVIIA Home Assistant Integration. It provides an API to retrieve files from a THIES Data Logger via an FTP server and upload them to a Microsoft SharePoint folder using the SharePoint REST API.
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
pip install saviialib
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Usage
|
|
40
|
-
|
|
41
|
-
### Initialize the EPii API Client
|
|
42
|
-
To start using the library, you need to create an `EpiiAPI` client instance with its configuration class:
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
from saviialib import EpiiAPI, EpiiAPIConfig
|
|
46
|
-
config = EpiiAPIConfig(
|
|
47
|
-
ftp_port=FTP_PORT,
|
|
48
|
-
ftp_host=FTP_HOST,
|
|
49
|
-
ftp_user=FTP_USER,
|
|
50
|
-
ftp_password=FTP_PASSWORD,
|
|
51
|
-
sharepoint_client_id=SHAREPOINT_CLIENT_ID,
|
|
52
|
-
sharepoint_client_secret=SHAREPOINT_CLIENT_SECRET,
|
|
53
|
-
sharepoint_tenant_id=SHAREPOINT_TENANT_ID,
|
|
54
|
-
sharepoint_tenant_name=SHAREPOINT_TENANT_NAME,
|
|
55
|
-
sharepoint_site_name=SHAREPOINT_SITE_NAME
|
|
56
|
-
)
|
|
57
|
-
api_client = EpiiAPI(config)
|
|
58
|
-
```
|
|
59
|
-
**Notes:**
|
|
60
|
-
- 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.
|
|
61
|
-
|
|
62
|
-
### Update THIES Data Logger Files
|
|
63
|
-
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:
|
|
64
|
-
|
|
65
|
-
```python
|
|
66
|
-
from saviialib import EpiiUpdateThiesConfig
|
|
67
|
-
import asyncio
|
|
68
|
-
|
|
69
|
-
async def main():
|
|
70
|
-
# Before calling this method, you must have initialised the api class ...
|
|
71
|
-
response = await api_client.update_thies_data()
|
|
72
|
-
return response
|
|
73
|
-
|
|
74
|
-
asyncio.run(main())
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Development
|
|
78
|
-
|
|
79
|
-
This project includes a `Makefile` to simplify common tasks. Below are the available commands:
|
|
80
|
-
|
|
81
|
-
### Install Basic Dependencies
|
|
82
|
-
To install the basic dependencies required for the project, run the following command:
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
make install-deps
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
This will ensure that all necessary libraries and tools are installed for the project to function properly.
|
|
89
|
-
|
|
90
|
-
### Install Development Requirements
|
|
91
|
-
For setting up a development environment with additional tools and libraries, execute:
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
make dev
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
This command installs all the dependencies needed for development, including testing and linting tools.
|
|
98
|
-
|
|
99
|
-
### Run Tests
|
|
100
|
-
To verify that the code is functioning as expected, you can run the test suite using:
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
make test
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
This will execute all the tests in the project and provide a summary of the results.
|
|
107
|
-
|
|
108
|
-
### Lint the Code
|
|
109
|
-
To ensure that the code adheres to the project's style guidelines and is free of common errors, run:
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
make lint
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
This command checks the codebase for linting issues and outputs any problems that need to be addressed.
|
|
116
|
-
|
|
117
|
-
## Contributing
|
|
118
|
-
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.
|
|
119
|
-
Contributions are welcome and appreciated!
|
|
120
|
-
|
|
121
|
-
## License
|
|
122
|
-
|
|
123
|
-
`saviialib` was created by Pedro Pablo Zavala Tejos. It is licensed under the terms of the MIT license.
|
|
124
|
-
|
|
File without changes
|
|
File without changes
|