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.
Files changed (59) hide show
  1. digitalkin/__init__.py +18 -0
  2. digitalkin/__version__.py +11 -0
  3. digitalkin/grpc/__init__.py +31 -0
  4. digitalkin/grpc/_base_server.py +488 -0
  5. digitalkin/grpc/module_server.py +233 -0
  6. digitalkin/grpc/module_servicer.py +304 -0
  7. digitalkin/grpc/registry_server.py +63 -0
  8. digitalkin/grpc/registry_servicer.py +451 -0
  9. digitalkin/grpc/utils/exceptions.py +33 -0
  10. digitalkin/grpc/utils/factory.py +178 -0
  11. digitalkin/grpc/utils/models.py +169 -0
  12. digitalkin/grpc/utils/types.py +24 -0
  13. digitalkin/logger.py +17 -0
  14. digitalkin/models/__init__.py +11 -0
  15. digitalkin/models/module/__init__.py +5 -0
  16. digitalkin/models/module/module.py +31 -0
  17. digitalkin/models/services/__init__.py +6 -0
  18. digitalkin/models/services/cost.py +53 -0
  19. digitalkin/models/services/storage.py +10 -0
  20. digitalkin/modules/__init__.py +7 -0
  21. digitalkin/modules/_base_module.py +177 -0
  22. digitalkin/modules/archetype_module.py +14 -0
  23. digitalkin/modules/job_manager.py +158 -0
  24. digitalkin/modules/tool_module.py +14 -0
  25. digitalkin/modules/trigger_module.py +14 -0
  26. digitalkin/py.typed +0 -0
  27. digitalkin/services/__init__.py +28 -0
  28. digitalkin/services/agent/__init__.py +6 -0
  29. digitalkin/services/agent/agent_strategy.py +22 -0
  30. digitalkin/services/agent/default_agent.py +16 -0
  31. digitalkin/services/cost/__init__.py +6 -0
  32. digitalkin/services/cost/cost_strategy.py +15 -0
  33. digitalkin/services/cost/default_cost.py +13 -0
  34. digitalkin/services/default_service.py +13 -0
  35. digitalkin/services/development_service.py +10 -0
  36. digitalkin/services/filesystem/__init__.py +6 -0
  37. digitalkin/services/filesystem/default_filesystem.py +29 -0
  38. digitalkin/services/filesystem/filesystem_strategy.py +31 -0
  39. digitalkin/services/identity/__init__.py +6 -0
  40. digitalkin/services/identity/default_identity.py +15 -0
  41. digitalkin/services/identity/identity_strategy.py +12 -0
  42. digitalkin/services/registry/__init__.py +6 -0
  43. digitalkin/services/registry/default_registry.py +13 -0
  44. digitalkin/services/registry/registry_strategy.py +17 -0
  45. digitalkin/services/service_provider.py +27 -0
  46. digitalkin/services/snapshot/__init__.py +6 -0
  47. digitalkin/services/snapshot/default_snapshot.py +39 -0
  48. digitalkin/services/snapshot/snapshot_strategy.py +31 -0
  49. digitalkin/services/storage/__init__.py +6 -0
  50. digitalkin/services/storage/default_storage.py +91 -0
  51. digitalkin/services/storage/grpc_storage.py +207 -0
  52. digitalkin/services/storage/storage_strategy.py +42 -0
  53. digitalkin/utils/__init__.py +1 -0
  54. digitalkin/utils/arg_parser.py +136 -0
  55. digitalkin-0.1.1.dist-info/METADATA +588 -0
  56. digitalkin-0.1.1.dist-info/RECORD +59 -0
  57. digitalkin-0.1.1.dist-info/WHEEL +5 -0
  58. digitalkin-0.1.1.dist-info/licenses/LICENSE +430 -0
  59. 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)