saviialib 0.9.1__py3-none-any.whl → 1.6.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.

Files changed (96) hide show
  1. saviialib/__init__.py +73 -3
  2. saviialib/general_types/api/__init__.py +0 -3
  3. saviialib/general_types/api/{epii_api_types.py → saviia_api_types.py} +4 -38
  4. saviialib/general_types/api/saviia_backup_api_types.py +24 -0
  5. saviialib/general_types/api/saviia_netcamera_api_types.py +11 -0
  6. saviialib/general_types/api/saviia_shakes_api_types.py +21 -0
  7. saviialib/general_types/api/saviia_thies_api_types.py +31 -0
  8. saviialib/general_types/error_types/api/{epii_api_error_types.py → saviia_api_error_types.py} +20 -0
  9. saviialib/general_types/error_types/api/saviia_netcamera_error_types.py +7 -0
  10. saviialib/general_types/error_types/common/common_types.py +9 -0
  11. saviialib/libs/directory_client/__init__.py +4 -0
  12. saviialib/libs/directory_client/client/os_client.py +55 -0
  13. saviialib/libs/directory_client/directory_client.py +44 -0
  14. saviialib/libs/directory_client/directory_client_contract.py +40 -0
  15. saviialib/libs/directory_client/types/directory_client_types.py +6 -0
  16. saviialib/libs/ffmpeg_client/__init__.py +8 -0
  17. saviialib/libs/ffmpeg_client/clients/ffmpeg_asyncio_client.py +101 -0
  18. saviialib/libs/ffmpeg_client/ffmpeg_client.py +25 -0
  19. saviialib/libs/ffmpeg_client/ffmpeg_client_contract.py +12 -0
  20. saviialib/libs/ffmpeg_client/types/ffmpeg_client_types.py +28 -0
  21. saviialib/libs/files_client/__init__.py +2 -2
  22. saviialib/libs/files_client/clients/aiofiles_client.py +26 -3
  23. saviialib/libs/files_client/clients/csv_client.py +42 -0
  24. saviialib/libs/files_client/files_client.py +5 -7
  25. saviialib/libs/files_client/types/files_client_types.py +5 -4
  26. saviialib/libs/ftp_client/clients/aioftp_client.py +13 -6
  27. saviialib/libs/ftp_client/clients/ftplib_client.py +58 -0
  28. saviialib/libs/ftp_client/ftp_client.py +8 -5
  29. saviialib/libs/ftp_client/ftp_client_contract.py +2 -2
  30. saviialib/libs/log_client/__init__.py +19 -0
  31. saviialib/libs/log_client/log_client.py +46 -0
  32. saviialib/libs/log_client/log_client_contract.py +28 -0
  33. saviialib/libs/log_client/logging_client/logging_client.py +58 -0
  34. saviialib/libs/log_client/types/log_client_types.py +47 -0
  35. saviialib/libs/log_client/utils/log_client_utils.py +6 -0
  36. saviialib/libs/sftp_client/__init__.py +8 -0
  37. saviialib/libs/sftp_client/clients/asyncssh_sftp_client.py +83 -0
  38. saviialib/libs/sftp_client/sftp_client.py +26 -0
  39. saviialib/libs/sftp_client/sftp_client_contract.py +13 -0
  40. saviialib/libs/sftp_client/types/sftp_client_types.py +24 -0
  41. saviialib/libs/sharepoint_client/__init__.py +2 -0
  42. saviialib/libs/sharepoint_client/clients/sharepoint_rest_api.py +31 -6
  43. saviialib/libs/sharepoint_client/sharepoint_client.py +25 -1
  44. saviialib/libs/sharepoint_client/sharepoint_client_contract.py +5 -0
  45. saviialib/libs/sharepoint_client/types/sharepoint_client_types.py +5 -0
  46. saviialib/libs/zero_dependency/utils/booleans_utils.py +2 -0
  47. saviialib/libs/zero_dependency/utils/datetime_utils.py +1 -1
  48. saviialib/libs/zero_dependency/utils/strings_utils.py +5 -0
  49. saviialib/services/backup/api.py +36 -0
  50. saviialib/services/backup/controllers/__init__.py +0 -0
  51. saviialib/services/{epii → backup}/controllers/types/__init__.py +1 -1
  52. saviialib/services/{epii → backup}/controllers/types/upload_backup_to_sharepoint_types.py +4 -2
  53. saviialib/services/{epii → backup}/controllers/upload_backup_to_sharepoint.py +9 -8
  54. saviialib/services/backup/use_cases/constants/upload_backup_to_sharepoint_constants.py +5 -0
  55. saviialib/services/{epii → backup}/use_cases/types/__init__.py +1 -1
  56. saviialib/services/{epii → backup}/use_cases/types/upload_backup_to_sharepoint_types.py +4 -2
  57. saviialib/services/backup/use_cases/upload_backup_to_sharepoint.py +474 -0
  58. saviialib/services/backup/utils/__init__.py +3 -0
  59. saviialib/services/backup/utils/upload_backup_to_sharepoint_utils.py +100 -0
  60. saviialib/services/netcamera/api.py +30 -0
  61. saviialib/services/netcamera/controllers/get_media_files.py +40 -0
  62. saviialib/services/netcamera/controllers/types/get_media_files_types.py +16 -0
  63. saviialib/services/netcamera/use_cases/get_media_files.py +76 -0
  64. saviialib/services/netcamera/use_cases/types/get_media_files_types.py +18 -0
  65. saviialib/services/shakes/__init__.py +0 -0
  66. saviialib/services/shakes/api.py +31 -0
  67. saviialib/services/shakes/controllers/get_miniseed_files.py +48 -0
  68. saviialib/services/shakes/controllers/types/get_miniseed_files_types.py +16 -0
  69. saviialib/services/shakes/use_cases/get_miniseed_files.py +79 -0
  70. saviialib/services/shakes/use_cases/types/get_miniseed_files_types.py +18 -0
  71. saviialib/services/shakes/use_cases/utils/get_miniseed_files_utils.py +11 -0
  72. saviialib/services/thies/__init__.py +0 -0
  73. saviialib/services/thies/api.py +42 -0
  74. saviialib/services/thies/constants/update_thies_data_constants.py +67 -0
  75. saviialib/services/{epii → thies}/controllers/types/update_thies_data_types.py +5 -4
  76. saviialib/services/{epii → thies}/controllers/update_thies_data.py +18 -6
  77. saviialib/services/thies/use_cases/components/create_thies_statistics_file.py +115 -0
  78. saviialib/services/thies/use_cases/components/thies_bp.py +442 -0
  79. saviialib/services/{epii → thies}/use_cases/types/update_thies_data_types.py +10 -2
  80. saviialib/services/thies/use_cases/update_thies_data.py +391 -0
  81. saviialib-1.6.0.dist-info/METADATA +126 -0
  82. saviialib-1.6.0.dist-info/RECORD +96 -0
  83. {saviialib-0.9.1.dist-info → saviialib-1.6.0.dist-info}/WHEEL +1 -1
  84. saviialib/services/epii/api.py +0 -80
  85. saviialib/services/epii/use_cases/constants/update_thies_data_constants.py +0 -5
  86. saviialib/services/epii/use_cases/constants/upload_backup_to_sharepoint_constants.py +0 -5
  87. saviialib/services/epii/use_cases/update_thies_data.py +0 -171
  88. saviialib/services/epii/use_cases/upload_backup_to_sharepoint.py +0 -241
  89. saviialib/services/epii/utils/__init__.py +0 -3
  90. saviialib/services/epii/utils/upload_backup_to_sharepoint_utils.py +0 -102
  91. saviialib-0.9.1.dist-info/METADATA +0 -120
  92. saviialib-0.9.1.dist-info/RECORD +0 -49
  93. /saviialib/{services/epii → libs/log_client/types}/__init__.py +0 -0
  94. /saviialib/services/{epii/controllers → backup}/__init__.py +0 -0
  95. /saviialib/services/{epii → thies}/utils/update_thies_data_utils.py +0 -0
  96. {saviialib-0.9.1.dist-info → saviialib-1.6.0.dist-info/licenses}/LICENSE +0 -0
@@ -1,21 +1,44 @@
1
1
  import aiofiles
2
-
2
+ from saviialib.libs.directory_client.directory_client import (
3
+ DirectoryClient,
4
+ DirectoryClientArgs,
5
+ )
3
6
  from saviialib.libs.files_client.files_client_contract import FilesClientContract
4
7
  from saviialib.libs.files_client.types.files_client_types import (
5
8
  FilesClientInitArgs,
6
9
  ReadArgs,
7
10
  WriteArgs,
8
11
  )
12
+ import json
9
13
 
10
14
 
11
15
  class AioFilesClient(FilesClientContract):
12
16
  def __init__(self, args: FilesClientInitArgs):
13
- pass
17
+ self.dir_client = DirectoryClient(DirectoryClientArgs(client_name="os_client"))
14
18
 
15
19
  async def read(self, args: ReadArgs) -> str | bytes:
20
+ if args.mode == "json":
21
+ async with aiofiles.open(
22
+ args.file_path, "r", encoding=args.encoding or "utf-8"
23
+ ) as file:
24
+ content = await file.read()
25
+ return json.loads(content)
26
+
16
27
  encoding = None if args.mode == "rb" else args.encoding
17
28
  async with aiofiles.open(args.file_path, args.mode, encoding=encoding) as file:
18
29
  return await file.read()
19
30
 
20
31
  async def write(self, args: WriteArgs) -> None:
21
- return None
32
+ file_path = (
33
+ self.dir_client.join_paths(args.destination_path, args.file_name)
34
+ if args.destination_path
35
+ else args.file_name
36
+ )
37
+ if args.mode == "json":
38
+ async with aiofiles.open(file_path, "w", encoding="utf-8") as file:
39
+ json_str = json.dumps(args.file_content, ensure_ascii=False, indent=2)
40
+ await file.write(json_str)
41
+ return
42
+
43
+ async with aiofiles.open(file_path, args.mode) as file:
44
+ await file.write(args.file_content)
@@ -0,0 +1,42 @@
1
+ import csv
2
+ from asyncio import to_thread
3
+
4
+ from saviialib.libs.directory_client.directory_client import (
5
+ DirectoryClient,
6
+ DirectoryClientArgs,
7
+ )
8
+ from saviialib.libs.files_client.files_client_contract import FilesClientContract
9
+ from saviialib.libs.files_client.types.files_client_types import (
10
+ FilesClientInitArgs,
11
+ ReadArgs,
12
+ WriteArgs,
13
+ )
14
+
15
+
16
+ class CsvClient(FilesClientContract):
17
+ def __init__(self, args: FilesClientInitArgs):
18
+ self.dir_client = DirectoryClient(DirectoryClientArgs(client_name="os_client"))
19
+
20
+ async def read(self, args: ReadArgs) -> str | bytes:
21
+ raise OSError("This method is not implemented yet.")
22
+
23
+ async def write(self, args: WriteArgs) -> None:
24
+ file_type = args.file_name.split(".")[-1]
25
+ file_content = args.file_content
26
+ header = file_content[0].keys()
27
+ if file_type == "tsv":
28
+ delimiter = "\t"
29
+ else: # Default CSV.
30
+ delimiter = ","
31
+
32
+ if args.destination_path == "":
33
+ dest_path = self.dir_client.join_paths(args.file_name)
34
+ else:
35
+ dest_path = self.dir_client.join_paths(
36
+ args.destination_path, args.file_name
37
+ )
38
+
39
+ with open(dest_path, "w", newline="") as file:
40
+ writer = csv.DictWriter(file, fieldnames=header, delimiter=delimiter) # type: ignore
41
+ await to_thread(writer.writeheader)
42
+ await to_thread(writer.writerows, file_content) # type: ignore
@@ -1,10 +1,11 @@
1
1
  from .clients.aiofiles_client import AioFilesClient
2
+ from .clients.csv_client import CsvClient
2
3
  from .files_client_contract import FilesClientContract
3
4
  from .types.files_client_types import FilesClientInitArgs, ReadArgs, WriteArgs
4
5
 
5
6
 
6
7
  class FilesClient(FilesClientContract):
7
- CLIENTS = {"aiofiles_client"}
8
+ CLIENTS = {"aiofiles_client", "csv_client"}
8
9
 
9
10
  def __init__(self, args: FilesClientInitArgs) -> None:
10
11
  if args.client_name not in FilesClient.CLIENTS:
@@ -13,15 +14,12 @@ class FilesClient(FilesClientContract):
13
14
 
14
15
  if args.client_name == "aiofiles_client":
15
16
  self.client_obj = AioFilesClient(args)
17
+ elif args.client_name == "csv_client":
18
+ self.client_obj = CsvClient(args)
19
+
16
20
  self.client_name = args.client_name
17
21
 
18
22
  async def read(self, args: ReadArgs):
19
- """Reads data from a specified source using the provided arguments.
20
-
21
- :param args (ReadArgs): An object containing the parameters required for the read operation.
22
-
23
- :return file: The result of the read operation, as returned by the client object.
24
- """
25
23
  return await self.client_obj.read(args)
26
24
 
27
25
  async def write(self, args: WriteArgs):
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Literal
2
+ from typing import Literal, Union, List, Dict
3
3
 
4
4
 
5
5
  @dataclass
@@ -20,12 +20,13 @@ class ReadArgs:
20
20
  """
21
21
 
22
22
  file_path: str
23
- mode: Literal["r", "rb"]
23
+ mode: Literal["r", "rb", "json"]
24
24
  encoding: str = "utf-8"
25
25
 
26
26
 
27
27
  @dataclass
28
28
  class WriteArgs:
29
- destination_path: str
30
29
  file_name: str
31
- file_content: str | bytes
30
+ file_content: Union[str, bytes, List[Dict]]
31
+ mode: Literal["w", "wb", "a", "json"]
32
+ destination_path: str = ""
@@ -34,12 +34,19 @@ class AioFTPClient(FTPClientContract):
34
34
  )
35
35
 
36
36
  async def list_files(self, args: FtpListFilesArgs) -> list[str]:
37
- await self._async_start()
38
- return [
39
- path.name async for path, _ in self.client.list(args.path, recursive=False)
40
- ]
37
+ try:
38
+ await self._async_start()
39
+ files = []
40
+ async for path, info in self.client.list(args.path, recursive=False):
41
+ files.append((path.name, int((info.get("size", 0)))))
42
+ return files
43
+ except StatusCodeError as error:
44
+ raise ConnectionAbortedError(error)
41
45
 
42
46
  async def read_file(self, args: FtpReadFileArgs) -> bytes:
43
47
  await self._async_start()
44
- async with self.client.download_stream(args.file_path) as stream:
45
- return await stream.read()
48
+ try:
49
+ async with self.client.download_stream(args.file_path) as stream: # type: ignore
50
+ return await stream.read()
51
+ except StatusCodeError as error:
52
+ raise FileNotFoundError(f"File not found: {args.file_path}") from error
@@ -0,0 +1,58 @@
1
+ import ftplib
2
+ import asyncio
3
+ from io import BytesIO
4
+ from saviialib.libs.ftp_client.ftp_client_contract import (
5
+ FTPClientContract,
6
+ )
7
+ from saviialib.libs.ftp_client.types.ftp_client_types import (
8
+ FtpClientInitArgs,
9
+ FtpListFilesArgs,
10
+ FtpReadFileArgs,
11
+ )
12
+
13
+
14
+ class FtplibClient(FTPClientContract):
15
+ def __init__(self, args: FtpClientInitArgs) -> None:
16
+ self.host = args.config.ftp_host
17
+ self.port = args.config.ftp_port
18
+ self.password = args.config.ftp_password
19
+ self.user = args.config.ftp_user
20
+ self.client = ftplib.FTP(host=self.host, user=self.user, passwd=self.password)
21
+
22
+ async def _async_start(self) -> None:
23
+ try:
24
+ await asyncio.to_thread(self.client.login, self.user, self.password)
25
+ except OSError:
26
+ raise ConnectionRefusedError(
27
+ f"{self.host}:{self.port} isn't active. "
28
+ "Please ensure the server is running and accessible."
29
+ )
30
+ except Exception as error:
31
+ raise ConnectionError(
32
+ f"General connection for {self.host}:{self.port}.", error.__str__()
33
+ )
34
+
35
+ async def list_files(self, args: FtpListFilesArgs) -> list[str]:
36
+ try:
37
+ EXCLUDED_NAMES = [".", ".."]
38
+ await self._async_start()
39
+ await asyncio.to_thread(self.client.cwd, args.path)
40
+ filenames = await asyncio.to_thread(self.client.nlst, args.path)
41
+ return [
42
+ filename for filename in filenames if filename not in EXCLUDED_NAMES
43
+ ]
44
+ except Exception as error:
45
+ raise ConnectionAbortedError(error)
46
+
47
+ async def read_file(self, args: FtpReadFileArgs) -> bytes:
48
+ await self._async_start()
49
+ try:
50
+ file_content = BytesIO()
51
+ await asyncio.to_thread(
52
+ self.client.retrbinary, "RETR " + args.file_path, file_content.write
53
+ )
54
+ await asyncio.to_thread(file_content.seek, 0)
55
+ file_bytes = await asyncio.to_thread(file_content.read)
56
+ return file_bytes
57
+ except Exception as error:
58
+ raise FileNotFoundError(f"File not found: {args.file_path}") from error
@@ -1,10 +1,11 @@
1
1
  from .clients.aioftp_client import AioFTPClient
2
+ from .clients.ftplib_client import FtplibClient
2
3
  from .ftp_client_contract import FTPClientContract
3
4
  from .types.ftp_client_types import FtpClientInitArgs, FtpListFilesArgs, FtpReadFileArgs
4
5
 
5
6
 
6
7
  class FTPClient(FTPClientContract):
7
- CLIENTS = {"aioftp_client"}
8
+ CLIENTS = {"aioftp_client", "ftplib_client"}
8
9
 
9
10
  def __init__(self, args: FtpClientInitArgs) -> None:
10
11
  if args.client_name not in FTPClient.CLIENTS:
@@ -13,10 +14,12 @@ class FTPClient(FTPClientContract):
13
14
 
14
15
  if args.client_name == "aioftp_client":
15
16
  self.client_obj = AioFTPClient(args)
17
+ elif args.client_name == "ftplib_client":
18
+ self.client_obj = FtplibClient(args)
16
19
  self.client_name = args.client_name
17
20
 
18
- def list_files(self, args: FtpListFilesArgs) -> list[str]:
19
- return self.client_obj.list_files(args)
21
+ async def list_files(self, args: FtpListFilesArgs) -> list[str]:
22
+ return await self.client_obj.list_files(args)
20
23
 
21
- def read_file(self, args: FtpReadFileArgs) -> bytes:
22
- return self.client_obj.read_file(args)
24
+ async def read_file(self, args: FtpReadFileArgs) -> bytes:
25
+ return await self.client_obj.read_file(args)
@@ -5,9 +5,9 @@ from .types.ftp_client_types import FtpListFilesArgs, FtpReadFileArgs
5
5
 
6
6
  class FTPClientContract(ABC):
7
7
  @abstractmethod
8
- def list_files(self, args: FtpListFilesArgs) -> list[str]:
8
+ async def list_files(self, args: FtpListFilesArgs) -> list[str]:
9
9
  pass
10
10
 
11
11
  @abstractmethod
12
- def read_file(self, args: FtpReadFileArgs) -> bytes:
12
+ async def read_file(self, args: FtpReadFileArgs) -> bytes:
13
13
  pass
@@ -0,0 +1,19 @@
1
+ from saviialib.libs.log_client.log_client import LogClient
2
+ from .types.log_client_types import (
3
+ InfoArgs,
4
+ DebugArgs,
5
+ ErrorArgs,
6
+ WarningArgs,
7
+ LogClientArgs,
8
+ LogStatus,
9
+ )
10
+
11
+ __all__ = [
12
+ "LogClient",
13
+ "InfoArgs",
14
+ "DebugArgs",
15
+ "ErrorArgs",
16
+ "WarningArgs",
17
+ "LogClientArgs",
18
+ "LogStatus",
19
+ ]
@@ -0,0 +1,46 @@
1
+ # Internal modules
2
+ from saviialib.libs.log_client.log_client_contract import LogClientContract
3
+ from saviialib.libs.log_client.logging_client.logging_client import LoggingClient
4
+ from saviialib.libs.log_client.types.log_client_types import (
5
+ ErrorArgs,
6
+ InfoArgs,
7
+ LogClientArgs,
8
+ DebugArgs,
9
+ WarningArgs,
10
+ )
11
+
12
+
13
+ class LogClient(LogClientContract):
14
+ def __init__(self, args: LogClientArgs):
15
+ clients = {
16
+ "logging": LoggingClient(args),
17
+ }
18
+ if clients.get(args.client_name):
19
+ self.client_name = args.client_name
20
+ self.client_obj = clients[args.client_name]
21
+ else:
22
+ raise KeyError(f"Unsupported client '{args.client_name}'.")
23
+
24
+ @property
25
+ def method_name(self):
26
+ return self.client_obj.method_name
27
+
28
+ @method_name.setter
29
+ def method_name(self, method_name: str):
30
+ self.client_obj.method_name = method_name
31
+
32
+ @property
33
+ def log_history(self):
34
+ return self.client_obj.log_history
35
+
36
+ def info(self, args: InfoArgs):
37
+ return self.client_obj.info(args)
38
+
39
+ def error(self, args: ErrorArgs):
40
+ return self.client_obj.error(args)
41
+
42
+ def debug(self, args: DebugArgs):
43
+ return self.client_obj.debug(args)
44
+
45
+ def warning(self, args: WarningArgs):
46
+ return self.client_obj.warning(args)
@@ -0,0 +1,28 @@
1
+ # External libraries
2
+ from abc import ABC, abstractmethod
3
+
4
+ # Internal modules
5
+ from saviialib.libs.log_client.types.log_client_types import (
6
+ ErrorArgs,
7
+ InfoArgs,
8
+ DebugArgs,
9
+ WarningArgs,
10
+ )
11
+
12
+
13
+ class LogClientContract(ABC):
14
+ @abstractmethod
15
+ def info(self, args: InfoArgs):
16
+ pass
17
+
18
+ @abstractmethod
19
+ def error(self, args: ErrorArgs):
20
+ pass
21
+
22
+ @abstractmethod
23
+ def debug(self, args: DebugArgs):
24
+ pass
25
+
26
+ @abstractmethod
27
+ def warning(self, args: WarningArgs):
28
+ pass
@@ -0,0 +1,58 @@
1
+ # External libraries
2
+ import logging
3
+
4
+ # Internal modules
5
+ from saviialib.libs.log_client.utils.log_client_utils import format_message
6
+ from saviialib.libs.log_client.log_client_contract import LogClientContract
7
+ from saviialib.libs.log_client.types.log_client_types import (
8
+ ErrorArgs,
9
+ InfoArgs,
10
+ LogClientArgs,
11
+ DebugArgs,
12
+ WarningArgs,
13
+ )
14
+
15
+ from saviialib.libs.zero_dependency.utils.datetime_utils import today, datetime_to_str
16
+
17
+
18
+ class LoggingClient(LogClientContract):
19
+ def __init__(self, args: LogClientArgs):
20
+ log_format = "{message}"
21
+ logging.basicConfig(format=log_format, level=logging.INFO, style="{")
22
+ self.class_name = args.class_name
23
+ self.method_name = args.method_name
24
+ self.active_record = args.active_record
25
+ self.log_history = []
26
+
27
+ def _save_to_history(self, meta: dict):
28
+ self.log_history.append(
29
+ f"[{datetime_to_str(today(), date_format='%m-%d-%Y %H:%M:%S')}][{self.class_name}] {meta.get('msg', '')}"
30
+ )
31
+
32
+ def info(self, args: InfoArgs) -> None:
33
+ if self.active_record:
34
+ self._save_to_history(args.metadata)
35
+ return logging.info(
36
+ format_message(self.class_name, self.method_name, args.status)
37
+ )
38
+
39
+ def error(self, args: ErrorArgs) -> None:
40
+ if self.active_record:
41
+ self._save_to_history(args.metadata)
42
+ return logging.error(
43
+ format_message(self.class_name, self.method_name, args.status)
44
+ )
45
+
46
+ def debug(self, args: DebugArgs) -> None:
47
+ if self.active_record:
48
+ self._save_to_history(args.metadata)
49
+ return logging.debug(
50
+ format_message(self.class_name, self.method_name, args.status)
51
+ )
52
+
53
+ def warning(self, args: WarningArgs) -> None:
54
+ if self.active_record:
55
+ self._save_to_history(args.metadata)
56
+ return logging.warning(
57
+ format_message(self.class_name, self.method_name, args.status)
58
+ )
@@ -0,0 +1,47 @@
1
+ from typing import Literal, Dict
2
+ from enum import Enum
3
+ from dataclasses import dataclass, field
4
+
5
+
6
+ class LogStatus(Enum):
7
+ STARTED = "started"
8
+ SUCCESSFUL = "successful"
9
+ FAILED = "failed"
10
+ ERROR = "error"
11
+ EARLY_RETURN = "early_return"
12
+ ALERT = "alert"
13
+
14
+
15
+ @dataclass
16
+ class LogClientArgs:
17
+ client_name: str = field(default="logging")
18
+ service_name: str = field(default="")
19
+ class_name: str = field(default="")
20
+ method_name: str = field(default="")
21
+ active_record: bool = False
22
+
23
+
24
+ @dataclass
25
+ class InfoArgs:
26
+ status: Literal[LogStatus.STARTED, LogStatus.SUCCESSFUL, LogStatus.EARLY_RETURN]
27
+ metadata: Dict = field(default_factory=dict)
28
+
29
+
30
+ @dataclass
31
+ class DebugArgs:
32
+ status: Literal[
33
+ LogStatus.STARTED, LogStatus.SUCCESSFUL, LogStatus.EARLY_RETURN, LogStatus.ALERT
34
+ ]
35
+ metadata: Dict = field(default_factory=dict)
36
+
37
+
38
+ @dataclass
39
+ class ErrorArgs:
40
+ status: Literal[LogStatus.ERROR]
41
+ metadata: Dict = field(default_factory=dict)
42
+
43
+
44
+ @dataclass
45
+ class WarningArgs:
46
+ status: Literal[LogStatus.FAILED, LogStatus.ALERT]
47
+ metadata: Dict = field(default_factory=dict)
@@ -0,0 +1,6 @@
1
+ # Internal libraries
2
+ from saviialib.libs.log_client.types.log_client_types import LogStatus
3
+
4
+
5
+ def format_message(class_name: str, method_name: str, status: LogStatus) -> str:
6
+ return f"{class_name}: {method_name}_{status.value}"
@@ -0,0 +1,8 @@
1
+ from .sftp_client import SFTPClient
2
+ from .types.sftp_client_types import (
3
+ ListfilesArgs,
4
+ DownloadfilesArgs,
5
+ SFTPClientInitArgs,
6
+ )
7
+
8
+ __all__ = ["SFTPClient", "ListfilesArgs", "DownloadfilesArgs", "SFTPClientInitArgs"]
@@ -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)
@@ -4,6 +4,7 @@ from .types.sharepoint_client_types import (
4
4
  SpListFilesArgs,
5
5
  SpListFoldersArgs,
6
6
  SpUploadFileArgs,
7
+ SpCreateFolderArgs,
7
8
  )
8
9
 
9
10
  __all__ = [
@@ -12,4 +13,5 @@ __all__ = [
12
13
  "SpListFilesArgs",
13
14
  "SpListFoldersArgs",
14
15
  "SpUploadFileArgs",
16
+ "SpCreateFolderArgs",
15
17
  ]