digitalkin 0.1.1__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.
- digitalkin/__init__.py +18 -0
- digitalkin/__version__.py +11 -0
- digitalkin/grpc/__init__.py +31 -0
- digitalkin/grpc/_base_server.py +488 -0
- digitalkin/grpc/module_server.py +233 -0
- digitalkin/grpc/module_servicer.py +304 -0
- digitalkin/grpc/registry_server.py +63 -0
- digitalkin/grpc/registry_servicer.py +451 -0
- digitalkin/grpc/utils/exceptions.py +33 -0
- digitalkin/grpc/utils/factory.py +178 -0
- digitalkin/grpc/utils/models.py +169 -0
- digitalkin/grpc/utils/types.py +24 -0
- digitalkin/logger.py +17 -0
- digitalkin/models/__init__.py +11 -0
- digitalkin/models/module/__init__.py +5 -0
- digitalkin/models/module/module.py +31 -0
- digitalkin/models/services/__init__.py +6 -0
- digitalkin/models/services/cost.py +53 -0
- digitalkin/models/services/storage.py +10 -0
- digitalkin/modules/__init__.py +7 -0
- digitalkin/modules/_base_module.py +177 -0
- digitalkin/modules/archetype_module.py +14 -0
- digitalkin/modules/job_manager.py +158 -0
- digitalkin/modules/tool_module.py +14 -0
- digitalkin/modules/trigger_module.py +14 -0
- digitalkin/py.typed +0 -0
- digitalkin/services/__init__.py +28 -0
- digitalkin/services/agent/__init__.py +6 -0
- digitalkin/services/agent/agent_strategy.py +22 -0
- digitalkin/services/agent/default_agent.py +16 -0
- digitalkin/services/cost/__init__.py +6 -0
- digitalkin/services/cost/cost_strategy.py +15 -0
- digitalkin/services/cost/default_cost.py +13 -0
- digitalkin/services/default_service.py +13 -0
- digitalkin/services/development_service.py +10 -0
- digitalkin/services/filesystem/__init__.py +6 -0
- digitalkin/services/filesystem/default_filesystem.py +29 -0
- digitalkin/services/filesystem/filesystem_strategy.py +31 -0
- digitalkin/services/identity/__init__.py +6 -0
- digitalkin/services/identity/default_identity.py +15 -0
- digitalkin/services/identity/identity_strategy.py +12 -0
- digitalkin/services/registry/__init__.py +6 -0
- digitalkin/services/registry/default_registry.py +13 -0
- digitalkin/services/registry/registry_strategy.py +17 -0
- digitalkin/services/service_provider.py +27 -0
- digitalkin/services/snapshot/__init__.py +6 -0
- digitalkin/services/snapshot/default_snapshot.py +39 -0
- digitalkin/services/snapshot/snapshot_strategy.py +31 -0
- digitalkin/services/storage/__init__.py +6 -0
- digitalkin/services/storage/default_storage.py +91 -0
- digitalkin/services/storage/grpc_storage.py +207 -0
- digitalkin/services/storage/storage_strategy.py +42 -0
- digitalkin/utils/__init__.py +1 -0
- digitalkin/utils/arg_parser.py +136 -0
- digitalkin-0.1.1.dist-info/METADATA +588 -0
- digitalkin-0.1.1.dist-info/RECORD +59 -0
- digitalkin-0.1.1.dist-info/WHEEL +5 -0
- digitalkin-0.1.1.dist-info/licenses/LICENSE +430 -0
- digitalkin-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""This module implements the default storage strategy."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import logging
|
|
5
|
+
from enum import Enum, auto
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import grpc
|
|
9
|
+
from digitalkin_proto.digitalkin.storage.v2 import data_pb2, storage_service_pb2_grpc
|
|
10
|
+
from google.protobuf import json_format
|
|
11
|
+
from google.protobuf.struct_pb2 import Struct
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
from digitalkin.grpc.utils.exceptions import ServerError
|
|
15
|
+
from digitalkin.grpc.utils.models import SecurityMode, ServerConfig
|
|
16
|
+
from digitalkin.services.storage.storage_strategy import StorageStrategy
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DataType(Enum):
|
|
22
|
+
"""."""
|
|
23
|
+
|
|
24
|
+
OUTPUT = auto()
|
|
25
|
+
VIEW = auto()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StorageData(BaseModel):
|
|
29
|
+
"""."""
|
|
30
|
+
|
|
31
|
+
data: dict[str, Any]
|
|
32
|
+
mission_id: str
|
|
33
|
+
name: str
|
|
34
|
+
timestamp: datetime.datetime
|
|
35
|
+
type: DataType
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GrpcStorage(StorageStrategy):
|
|
39
|
+
"""This class implements the default storage strategy."""
|
|
40
|
+
|
|
41
|
+
def _init_channel(self, config: ServerConfig) -> grpc.Channel:
|
|
42
|
+
"""Create an appropriate channel to the registry server.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
A gRPC channel for communication with the registry.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If credentials are required but not provided.
|
|
49
|
+
"""
|
|
50
|
+
if config.security == SecurityMode.SECURE and config.credentials:
|
|
51
|
+
# Secure channel
|
|
52
|
+
with open(config.credentials.server_cert_path, "rb") as cert_file: # noqa: FURB101
|
|
53
|
+
certificate_chain = cert_file.read()
|
|
54
|
+
|
|
55
|
+
root_certificates = None
|
|
56
|
+
if config.credentials.root_cert_path:
|
|
57
|
+
with open(config.credentials.root_cert_path, "rb") as root_cert_file: # noqa: FURB101
|
|
58
|
+
root_certificates = root_cert_file.read()
|
|
59
|
+
|
|
60
|
+
# Create channel credentials
|
|
61
|
+
channel_credentials = grpc.ssl_channel_credentials(root_certificates=root_certificates or certificate_chain)
|
|
62
|
+
|
|
63
|
+
return grpc.secure_channel(f"{config.host}:{config.port}", channel_credentials)
|
|
64
|
+
# Insecure channel
|
|
65
|
+
return grpc.insecure_channel(f"{config.host}:{config.port}")
|
|
66
|
+
|
|
67
|
+
def __post_init__(self, config: ServerConfig) -> None:
|
|
68
|
+
"""Init the channel from a config file.
|
|
69
|
+
|
|
70
|
+
Need to be call if the user register a gRPC channel.
|
|
71
|
+
"""
|
|
72
|
+
channel = self._init_channel(config)
|
|
73
|
+
self.stub = storage_service_pb2_grpc.StorageServiceStub(channel)
|
|
74
|
+
logger.info("Channel client 'storage' initialized succesfully")
|
|
75
|
+
|
|
76
|
+
def exec_grpc_query(self, query_endpoint: str, request: Any) -> Any:
|
|
77
|
+
"""."""
|
|
78
|
+
try:
|
|
79
|
+
# Call the register method
|
|
80
|
+
logger.warning("send request to %s", query_endpoint)
|
|
81
|
+
response = getattr(self.stub, query_endpoint)(request)
|
|
82
|
+
logger.warning("recive response from request to registry: %s", response)
|
|
83
|
+
|
|
84
|
+
if response.success:
|
|
85
|
+
logger.info("Module registered successfully")
|
|
86
|
+
else:
|
|
87
|
+
logger.error("Module registration failed")
|
|
88
|
+
return response
|
|
89
|
+
except grpc.RpcError:
|
|
90
|
+
logger.exception("RPC error during registration:")
|
|
91
|
+
raise ServerError
|
|
92
|
+
|
|
93
|
+
def connect(self) -> bool: # noqa: PLR6301
|
|
94
|
+
"""Establish connection to the database.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
bool: True if the connection is successful, False otherwise
|
|
98
|
+
"""
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
def disconnect(self) -> bool: # noqa: PLR6301
|
|
102
|
+
"""Close connection to the database.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
bool: True if the connection is closed, False otherwise
|
|
106
|
+
"""
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
def create(self, table: str, data: dict[str, Any]) -> str:
|
|
110
|
+
"""Create a new record in the database.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
str: The ID of the new record
|
|
114
|
+
"""
|
|
115
|
+
# Create a Struct for the data
|
|
116
|
+
data_struct = Struct()
|
|
117
|
+
if data.get("data"):
|
|
118
|
+
data_struct.update(data["data"])
|
|
119
|
+
|
|
120
|
+
request = data_pb2.StoreDataRequest(
|
|
121
|
+
data=data_struct,
|
|
122
|
+
mission_id=data["mission_id"],
|
|
123
|
+
name=data["name"],
|
|
124
|
+
type=data_pb2.DataType.Name(1),
|
|
125
|
+
)
|
|
126
|
+
return self.exec_grpc_query("StoreData", request)
|
|
127
|
+
|
|
128
|
+
def get_data_by_mission(self, table: str, data: dict[str, Any]):
|
|
129
|
+
request = data_pb2.GetDataByMissionRequest(mission_id=data["mission_id"])
|
|
130
|
+
response = self.exec_grpc_query("GetDataByMission", request)
|
|
131
|
+
return [
|
|
132
|
+
StorageData(
|
|
133
|
+
data=data.data,
|
|
134
|
+
mission_id=data.mission_id,
|
|
135
|
+
name=data.name,
|
|
136
|
+
timestamp=data.timestamp,
|
|
137
|
+
type=data.type,
|
|
138
|
+
)
|
|
139
|
+
for data in response.data_items
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
def get_data_by_name(self, table: str, data: dict[str, Any]):
|
|
143
|
+
request = data_pb2.GetDataByNameRequest(mission_id=data["mission_id"], name=data_pb2.DataType())
|
|
144
|
+
response = self.exec_grpc_query("GetDataByName", request)
|
|
145
|
+
return [
|
|
146
|
+
StorageData(
|
|
147
|
+
data=data.data,
|
|
148
|
+
mission_id=data.mission_id,
|
|
149
|
+
name=data.name,
|
|
150
|
+
timestamp=data.timestamp,
|
|
151
|
+
type=data.type,
|
|
152
|
+
)
|
|
153
|
+
for data in response.stored_data
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
def get_data_by_type(self, table: str, data: dict[str, Any]):
|
|
157
|
+
request = data_pb2.GetDataByTypeRequest(
|
|
158
|
+
mission_id=data["mission_id"],
|
|
159
|
+
type=data_pb2.DataType.Name(data["type"]),
|
|
160
|
+
)
|
|
161
|
+
response = self.exec_grpc_query("GetDataByType", request)
|
|
162
|
+
return [
|
|
163
|
+
StorageData(
|
|
164
|
+
data=json_format.MessageToDict(data.data),
|
|
165
|
+
mission_id=data.mission_id,
|
|
166
|
+
name=data.name,
|
|
167
|
+
timestamp=data.timestamp,
|
|
168
|
+
type=data.type,
|
|
169
|
+
)
|
|
170
|
+
for data in response.stored_data
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
def delete(self, table: str, data: dict[str, Any]) -> int:
|
|
174
|
+
"""Delete records from the database.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
int: The number of records deleted
|
|
178
|
+
"""
|
|
179
|
+
request = data_pb2.DeleteDataRequest(
|
|
180
|
+
mission_id=data["mission_id"],
|
|
181
|
+
name=data_pb2.DataType.Name(data["name"]),
|
|
182
|
+
)
|
|
183
|
+
return self.exec_grpc_query("DeleteData", request)
|
|
184
|
+
|
|
185
|
+
def get(self, table: str, data: dict[str, Any]) -> list[dict[str, Any]]:
|
|
186
|
+
"""Get records from the database.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
list[dict[str, Any]]: The list of records
|
|
190
|
+
"""
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
def update(self, table: str, data: dict[str, Any]) -> int:
|
|
194
|
+
"""Update records in the database.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
int: The number of records updated
|
|
198
|
+
"""
|
|
199
|
+
return 1
|
|
200
|
+
|
|
201
|
+
def get_all(self) -> dict[str, list[dict[str, Any]]]:
|
|
202
|
+
"""Get all records from the database.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
dict[str, list[dict[str, Any]]]: table with respective list of records
|
|
206
|
+
"""
|
|
207
|
+
return {}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""This module contains the abstract base class for storage strategies."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StorageStrategy(ABC):
|
|
8
|
+
"""Abstract base class for storage strategies."""
|
|
9
|
+
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
"""Initialize the storage strategy."""
|
|
12
|
+
|
|
13
|
+
def __post_init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003
|
|
14
|
+
"""Initialize the storage strategy."""
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def connect(self) -> bool:
|
|
18
|
+
"""Establish connection to the database."""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def disconnect(self) -> bool:
|
|
22
|
+
"""Close connection to the database."""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def create(self, table: str, data: dict[str, Any]) -> str:
|
|
26
|
+
"""Create a new record in the database."""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def get(self, table: str, data: dict[str, Any]) -> list[dict[str, Any]]:
|
|
30
|
+
"""Get records from the database."""
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def update(self, table: str, data: dict[str, Any]) -> int:
|
|
34
|
+
"""Update records in the database."""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def delete(self, table: str, data: dict[str, Any]) -> int:
|
|
38
|
+
"""Delete records from the database."""
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def get_all(self) -> dict[str, list[dict[str, Any]]]:
|
|
42
|
+
"""Get all records from the database."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""General utils folder."""
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""ArgParser and Action classes to ease command lines arguments settings."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from argparse import Action, ArgumentParser, Namespace, _HelpAction, _SubParsersAction # noqa: PLC2701
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from digitalkin.services.service_provider import ServiceProvider
|
|
10
|
+
|
|
11
|
+
logging.getLogger().setLevel(logging.INFO)
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArgParser:
|
|
16
|
+
"""ArgParse Abstract class to join all argparse argument in the same parser.
|
|
17
|
+
|
|
18
|
+
Custom help display to allow multiple parser and subparser help message.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
--------
|
|
22
|
+
Inherit this class in your base class.
|
|
23
|
+
Override '_add_parser_args', '_add_exclusive_args' and '_add_subparser_args'.
|
|
24
|
+
|
|
25
|
+
class WindowHandler(ArgParser):
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def _add_screen_parser_args(parser) -> None:
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"-f", "--fps", type=int, default=60, help="Screen FPS", dest="fps"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def _add_media_parser_args(self, parser) -> None:
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"-w",
|
|
36
|
+
"--workers",
|
|
37
|
+
type=int,
|
|
38
|
+
default=3,
|
|
39
|
+
help="Number of worker processing media in background",
|
|
40
|
+
dest="media_worker_count"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def _add_parser_args(self, parser) -> None:
|
|
44
|
+
super()._add_parser_args(parser)
|
|
45
|
+
self._add_screen_parser_args(parser)
|
|
46
|
+
self._add_media_parser_args(parser)
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
# init the parser
|
|
50
|
+
super().__init__()
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
args: Namespace
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
Override methods
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
class HelpAction(_HelpAction):
|
|
60
|
+
"""."""
|
|
61
|
+
|
|
62
|
+
def __call__(
|
|
63
|
+
self,
|
|
64
|
+
parser: ArgumentParser,
|
|
65
|
+
namespace: Namespace, # noqa: ARG002
|
|
66
|
+
values: str | Sequence[Any] | None, # noqa: ARG002
|
|
67
|
+
option_string: str | None = None, # noqa: ARG002
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Override the HelpActions as it doesn't handle subparser well."""
|
|
70
|
+
parser.print_help()
|
|
71
|
+
subparsers_actions = [action for action in parser._actions if isinstance(action, _SubParsersAction)] # noqa: SLF001
|
|
72
|
+
for subparsers_action in subparsers_actions:
|
|
73
|
+
for choice, subparser in subparsers_action.choices.items():
|
|
74
|
+
logger.info("Subparser '%s':\n%s", choice, subparser.format_help())
|
|
75
|
+
parser.exit()
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
Private methods
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def _add_parser_args(self, parser: ArgumentParser) -> None:
|
|
82
|
+
parser.add_argument("-h", "--help", action=self.HelpAction, help="help usage")
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def _add_exclusive_args(parser: ArgumentParser) -> None: ...
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _add_subparser_args(parser: ArgumentParser) -> None: ...
|
|
89
|
+
|
|
90
|
+
def __init__(self, prog: str = "PROG") -> None:
|
|
91
|
+
"""Create prser and call abstract methods."""
|
|
92
|
+
self.parser = ArgumentParser(prog=prog, conflict_handler="resolve", add_help=False)
|
|
93
|
+
self._add_parser_args(self.parser)
|
|
94
|
+
self._add_exclusive_args(self.parser)
|
|
95
|
+
self._add_subparser_args(self.parser)
|
|
96
|
+
self.args, _ = self.parser.parse_known_args()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DevelopmentModeMappingAction(Action):
|
|
100
|
+
"""."""
|
|
101
|
+
|
|
102
|
+
default: ServiceProvider | None
|
|
103
|
+
class_mapping: dict[str, ServiceProvider]
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
env_var: str,
|
|
108
|
+
class_mapping: dict[str, ServiceProvider],
|
|
109
|
+
required: bool = True, # noqa: FBT001, FBT002
|
|
110
|
+
default: str | None = None,
|
|
111
|
+
**kwargs: dict[str, Any],
|
|
112
|
+
) -> None:
|
|
113
|
+
"""."""
|
|
114
|
+
default = class_mapping.get(os.environ.get(env_var, default), None) # type: ignore
|
|
115
|
+
if default is None:
|
|
116
|
+
logger.error("Invalid default value: %s, for the Service Provider in the module", default)
|
|
117
|
+
|
|
118
|
+
if required and default:
|
|
119
|
+
required = False
|
|
120
|
+
self.class_mapping = class_mapping
|
|
121
|
+
super().__init__(default=default, required=required, **kwargs) # type: ignore
|
|
122
|
+
|
|
123
|
+
def __call__(
|
|
124
|
+
self,
|
|
125
|
+
parser: ArgumentParser,
|
|
126
|
+
namespace: Namespace,
|
|
127
|
+
values: str | Sequence[Any] | None,
|
|
128
|
+
option_string: str | None = None, # noqa: ARG002
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Set the attribute to the corresponding class."""
|
|
131
|
+
if values not in self.class_mapping:
|
|
132
|
+
logger.error("Invalid mode: %s, dest: %s not set!", values, self.dest)
|
|
133
|
+
parser.error(f"Invalid mode: {values}")
|
|
134
|
+
|
|
135
|
+
values = self.class_mapping[values] # type: ignore
|
|
136
|
+
setattr(namespace, self.dest, values)
|