digitalkin 0.3.2.dev2__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 (131) hide show
  1. base_server/__init__.py +1 -0
  2. base_server/mock/__init__.py +5 -0
  3. base_server/mock/mock_pb2.py +39 -0
  4. base_server/mock/mock_pb2_grpc.py +102 -0
  5. base_server/server_async_insecure.py +125 -0
  6. base_server/server_async_secure.py +143 -0
  7. base_server/server_sync_insecure.py +103 -0
  8. base_server/server_sync_secure.py +122 -0
  9. digitalkin/__init__.py +8 -0
  10. digitalkin/__version__.py +8 -0
  11. digitalkin/core/__init__.py +1 -0
  12. digitalkin/core/common/__init__.py +9 -0
  13. digitalkin/core/common/factories.py +156 -0
  14. digitalkin/core/job_manager/__init__.py +1 -0
  15. digitalkin/core/job_manager/base_job_manager.py +288 -0
  16. digitalkin/core/job_manager/single_job_manager.py +354 -0
  17. digitalkin/core/job_manager/taskiq_broker.py +311 -0
  18. digitalkin/core/job_manager/taskiq_job_manager.py +541 -0
  19. digitalkin/core/task_manager/__init__.py +1 -0
  20. digitalkin/core/task_manager/base_task_manager.py +539 -0
  21. digitalkin/core/task_manager/local_task_manager.py +108 -0
  22. digitalkin/core/task_manager/remote_task_manager.py +87 -0
  23. digitalkin/core/task_manager/surrealdb_repository.py +266 -0
  24. digitalkin/core/task_manager/task_executor.py +249 -0
  25. digitalkin/core/task_manager/task_session.py +406 -0
  26. digitalkin/grpc_servers/__init__.py +1 -0
  27. digitalkin/grpc_servers/_base_server.py +486 -0
  28. digitalkin/grpc_servers/module_server.py +208 -0
  29. digitalkin/grpc_servers/module_servicer.py +516 -0
  30. digitalkin/grpc_servers/utils/__init__.py +1 -0
  31. digitalkin/grpc_servers/utils/exceptions.py +29 -0
  32. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +88 -0
  33. digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
  34. digitalkin/grpc_servers/utils/utility_schema_extender.py +97 -0
  35. digitalkin/logger.py +157 -0
  36. digitalkin/mixins/__init__.py +19 -0
  37. digitalkin/mixins/base_mixin.py +10 -0
  38. digitalkin/mixins/callback_mixin.py +24 -0
  39. digitalkin/mixins/chat_history_mixin.py +110 -0
  40. digitalkin/mixins/cost_mixin.py +76 -0
  41. digitalkin/mixins/file_history_mixin.py +93 -0
  42. digitalkin/mixins/filesystem_mixin.py +46 -0
  43. digitalkin/mixins/logger_mixin.py +51 -0
  44. digitalkin/mixins/storage_mixin.py +79 -0
  45. digitalkin/models/__init__.py +8 -0
  46. digitalkin/models/core/__init__.py +1 -0
  47. digitalkin/models/core/job_manager_models.py +36 -0
  48. digitalkin/models/core/task_monitor.py +70 -0
  49. digitalkin/models/grpc_servers/__init__.py +1 -0
  50. digitalkin/models/grpc_servers/models.py +275 -0
  51. digitalkin/models/grpc_servers/types.py +24 -0
  52. digitalkin/models/module/__init__.py +25 -0
  53. digitalkin/models/module/module.py +40 -0
  54. digitalkin/models/module/module_context.py +149 -0
  55. digitalkin/models/module/module_types.py +393 -0
  56. digitalkin/models/module/utility.py +146 -0
  57. digitalkin/models/services/__init__.py +10 -0
  58. digitalkin/models/services/cost.py +54 -0
  59. digitalkin/models/services/registry.py +42 -0
  60. digitalkin/models/services/storage.py +44 -0
  61. digitalkin/modules/__init__.py +11 -0
  62. digitalkin/modules/_base_module.py +517 -0
  63. digitalkin/modules/archetype_module.py +23 -0
  64. digitalkin/modules/tool_module.py +23 -0
  65. digitalkin/modules/trigger_handler.py +48 -0
  66. digitalkin/modules/triggers/__init__.py +12 -0
  67. digitalkin/modules/triggers/healthcheck_ping_trigger.py +45 -0
  68. digitalkin/modules/triggers/healthcheck_services_trigger.py +63 -0
  69. digitalkin/modules/triggers/healthcheck_status_trigger.py +52 -0
  70. digitalkin/py.typed +0 -0
  71. digitalkin/services/__init__.py +30 -0
  72. digitalkin/services/agent/__init__.py +6 -0
  73. digitalkin/services/agent/agent_strategy.py +19 -0
  74. digitalkin/services/agent/default_agent.py +13 -0
  75. digitalkin/services/base_strategy.py +22 -0
  76. digitalkin/services/communication/__init__.py +7 -0
  77. digitalkin/services/communication/communication_strategy.py +76 -0
  78. digitalkin/services/communication/default_communication.py +101 -0
  79. digitalkin/services/communication/grpc_communication.py +223 -0
  80. digitalkin/services/cost/__init__.py +14 -0
  81. digitalkin/services/cost/cost_strategy.py +100 -0
  82. digitalkin/services/cost/default_cost.py +114 -0
  83. digitalkin/services/cost/grpc_cost.py +138 -0
  84. digitalkin/services/filesystem/__init__.py +7 -0
  85. digitalkin/services/filesystem/default_filesystem.py +417 -0
  86. digitalkin/services/filesystem/filesystem_strategy.py +252 -0
  87. digitalkin/services/filesystem/grpc_filesystem.py +317 -0
  88. digitalkin/services/identity/__init__.py +6 -0
  89. digitalkin/services/identity/default_identity.py +15 -0
  90. digitalkin/services/identity/identity_strategy.py +14 -0
  91. digitalkin/services/registry/__init__.py +27 -0
  92. digitalkin/services/registry/default_registry.py +141 -0
  93. digitalkin/services/registry/exceptions.py +47 -0
  94. digitalkin/services/registry/grpc_registry.py +306 -0
  95. digitalkin/services/registry/registry_models.py +43 -0
  96. digitalkin/services/registry/registry_strategy.py +98 -0
  97. digitalkin/services/services_config.py +200 -0
  98. digitalkin/services/services_models.py +65 -0
  99. digitalkin/services/setup/__init__.py +1 -0
  100. digitalkin/services/setup/default_setup.py +219 -0
  101. digitalkin/services/setup/grpc_setup.py +343 -0
  102. digitalkin/services/setup/setup_strategy.py +145 -0
  103. digitalkin/services/snapshot/__init__.py +6 -0
  104. digitalkin/services/snapshot/default_snapshot.py +39 -0
  105. digitalkin/services/snapshot/snapshot_strategy.py +30 -0
  106. digitalkin/services/storage/__init__.py +7 -0
  107. digitalkin/services/storage/default_storage.py +228 -0
  108. digitalkin/services/storage/grpc_storage.py +214 -0
  109. digitalkin/services/storage/storage_strategy.py +273 -0
  110. digitalkin/services/user_profile/__init__.py +12 -0
  111. digitalkin/services/user_profile/default_user_profile.py +55 -0
  112. digitalkin/services/user_profile/grpc_user_profile.py +69 -0
  113. digitalkin/services/user_profile/user_profile_strategy.py +40 -0
  114. digitalkin/utils/__init__.py +29 -0
  115. digitalkin/utils/arg_parser.py +92 -0
  116. digitalkin/utils/development_mode_action.py +51 -0
  117. digitalkin/utils/dynamic_schema.py +483 -0
  118. digitalkin/utils/llm_ready_schema.py +75 -0
  119. digitalkin/utils/package_discover.py +357 -0
  120. digitalkin-0.3.2.dev2.dist-info/METADATA +602 -0
  121. digitalkin-0.3.2.dev2.dist-info/RECORD +131 -0
  122. digitalkin-0.3.2.dev2.dist-info/WHEEL +5 -0
  123. digitalkin-0.3.2.dev2.dist-info/licenses/LICENSE +430 -0
  124. digitalkin-0.3.2.dev2.dist-info/top_level.txt +4 -0
  125. modules/__init__.py +0 -0
  126. modules/cpu_intensive_module.py +280 -0
  127. modules/dynamic_setup_module.py +338 -0
  128. modules/minimal_llm_module.py +347 -0
  129. modules/text_transform_module.py +203 -0
  130. services/filesystem_module.py +200 -0
  131. services/storage_module.py +206 -0
@@ -0,0 +1,223 @@
1
+ """gRPC client implementation for Communication service."""
2
+
3
+ import asyncio
4
+ from collections.abc import AsyncGenerator, Awaitable, Callable
5
+
6
+ from agentic_mesh_protocol.module.v1 import (
7
+ information_pb2,
8
+ lifecycle_pb2,
9
+ module_service_pb2_grpc,
10
+ )
11
+ from google.protobuf import json_format, struct_pb2
12
+
13
+ from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
14
+ from digitalkin.logger import logger
15
+ from digitalkin.models.grpc_servers.models import ClientConfig
16
+ from digitalkin.services.base_strategy import BaseStrategy
17
+ from digitalkin.services.communication.communication_strategy import CommunicationStrategy
18
+
19
+
20
+ class GrpcCommunication(CommunicationStrategy, GrpcClientWrapper):
21
+ """gRPC client for module-to-module communication.
22
+
23
+ This class provides methods to communicate with remote modules
24
+ using the Module Service gRPC protocol.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ mission_id: str,
30
+ setup_id: str,
31
+ setup_version_id: str,
32
+ client_config: ClientConfig,
33
+ ) -> None:
34
+ """Initialize the gRPC communication client.
35
+
36
+ Args:
37
+ mission_id: Mission identifier
38
+ setup_id: Setup identifier
39
+ setup_version_id: Setup version identifier
40
+ client_config: Client configuration for gRPC connection
41
+ """
42
+ BaseStrategy.__init__(self, mission_id, setup_id, setup_version_id)
43
+ self.client_config = client_config
44
+
45
+ logger.debug(
46
+ "Initialized GrpcCommunication",
47
+ extra={"security": client_config.security},
48
+ )
49
+
50
+ def _create_stub(self, module_address: str, module_port: int) -> module_service_pb2_grpc.ModuleServiceStub:
51
+ """Create a new stub for the target module.
52
+
53
+ Args:
54
+ module_address: Module host address
55
+ module_port: Module port
56
+
57
+ Returns:
58
+ ModuleServiceStub for the target module
59
+ """
60
+ logger.debug(
61
+ "Creating connection",
62
+ extra={"address": module_address, "port": module_port},
63
+ )
64
+
65
+ config = ClientConfig(
66
+ host=module_address,
67
+ port=module_port,
68
+ mode=self.client_config.mode,
69
+ security=self.client_config.security,
70
+ credentials=self.client_config.credentials,
71
+ channel_options=self.client_config.channel_options,
72
+ )
73
+
74
+ channel = self._init_channel(config)
75
+ return module_service_pb2_grpc.ModuleServiceStub(channel)
76
+
77
+ async def get_module_schemas(
78
+ self,
79
+ module_address: str,
80
+ module_port: int,
81
+ *,
82
+ llm_format: bool = False,
83
+ ) -> dict[str, dict]:
84
+ """Get module schemas via gRPC.
85
+
86
+ Args:
87
+ module_address: Target module address
88
+ module_port: Target module port
89
+ llm_format: Return LLM-friendly format
90
+
91
+ Returns:
92
+ Dictionary containing schemas
93
+ """
94
+ stub = self._create_stub(module_address, module_port)
95
+
96
+ # Create requests
97
+ input_request = information_pb2.GetModuleInputRequest(llm_format=llm_format)
98
+ output_request = information_pb2.GetModuleOutputRequest(llm_format=llm_format)
99
+ setup_request = information_pb2.GetModuleSetupRequest(llm_format=llm_format)
100
+ secret_request = information_pb2.GetModuleSecretRequest(llm_format=llm_format)
101
+
102
+ # Get all schemas in parallel
103
+ try:
104
+ input_response, output_response, setup_response, secret_response = await asyncio.gather(
105
+ asyncio.to_thread(stub.GetModuleInput, input_request),
106
+ asyncio.to_thread(stub.GetModuleOutput, output_request),
107
+ asyncio.to_thread(stub.GetModuleSetup, setup_request),
108
+ asyncio.to_thread(stub.GetModuleSecret, secret_request),
109
+ )
110
+
111
+ logger.debug(
112
+ "Retrieved module schemas",
113
+ extra={
114
+ "module_address": module_address,
115
+ "module_port": module_port,
116
+ "llm_format": llm_format,
117
+ },
118
+ )
119
+
120
+ return {
121
+ "input": json_format.MessageToDict(input_response.input_schema),
122
+ "output": json_format.MessageToDict(output_response.output_schema),
123
+ "setup": json_format.MessageToDict(setup_response.setup_schema),
124
+ "secret": json_format.MessageToDict(secret_response.secret_schema),
125
+ }
126
+ except Exception:
127
+ logger.exception(
128
+ "Failed to get module schemas",
129
+ extra={
130
+ "module_address": module_address,
131
+ "module_port": module_port,
132
+ },
133
+ )
134
+ raise
135
+
136
+ async def call_module(
137
+ self,
138
+ module_address: str,
139
+ module_port: int,
140
+ input_data: dict,
141
+ setup_id: str,
142
+ mission_id: str,
143
+ callback: Callable[[dict], Awaitable[None]] | None = None,
144
+ ) -> AsyncGenerator[dict, None]:
145
+ """Call a module and stream responses via gRPC.
146
+
147
+ Args:
148
+ module_address: Target module address
149
+ module_port: Target module port
150
+ input_data: Input data as dictionary
151
+ setup_id: Setup configuration ID
152
+ mission_id: Mission context ID
153
+ callback: Optional callback for each response
154
+
155
+ Yields:
156
+ Streaming responses from module as dictionaries
157
+ """
158
+ stub = self._create_stub(module_address, module_port)
159
+
160
+ # Convert input data to protobuf Struct
161
+ input_struct = struct_pb2.Struct()
162
+ input_struct.update(input_data)
163
+
164
+ # Create request
165
+ request = lifecycle_pb2.StartModuleRequest(
166
+ input=input_struct,
167
+ setup_id=setup_id,
168
+ mission_id=mission_id,
169
+ )
170
+
171
+ logger.debug(
172
+ "Calling module",
173
+ extra={
174
+ "module_address": module_address,
175
+ "module_port": module_port,
176
+ "setup_id": setup_id,
177
+ "mission_id": mission_id,
178
+ },
179
+ )
180
+
181
+ try:
182
+ # Call StartModule with streaming response
183
+ response_stream = stub.StartModule(request)
184
+
185
+ # Stream responses
186
+ for response in response_stream:
187
+ # Convert protobuf Struct to dict
188
+ output_dict = json_format.MessageToDict(response.output)
189
+
190
+ # Add job_id and success flag
191
+ response_dict = {
192
+ "success": response.success,
193
+ "job_id": response.job_id,
194
+ "output": output_dict,
195
+ }
196
+
197
+ logger.debug(
198
+ "Received module response",
199
+ extra={
200
+ "module_address": module_address,
201
+ "module_port": module_port,
202
+ "success": response.success,
203
+ "job_id": response.job_id,
204
+ },
205
+ )
206
+
207
+ # Call callback if provided
208
+ if callback:
209
+ await callback(response_dict)
210
+
211
+ yield response_dict
212
+
213
+ except Exception:
214
+ logger.exception(
215
+ "Failed to call module",
216
+ extra={
217
+ "module_address": module_address,
218
+ "module_port": module_port,
219
+ "setup_id": setup_id,
220
+ "mission_id": mission_id,
221
+ },
222
+ )
223
+ raise
@@ -0,0 +1,14 @@
1
+ """This module is responsible for handling the cost services."""
2
+
3
+ from digitalkin.services.cost.cost_strategy import CostConfig, CostData, CostStrategy, CostType
4
+ from digitalkin.services.cost.default_cost import DefaultCost
5
+ from digitalkin.services.cost.grpc_cost import GrpcCost
6
+
7
+ __all__ = [
8
+ "CostConfig",
9
+ "CostData",
10
+ "CostStrategy",
11
+ "CostType",
12
+ "DefaultCost",
13
+ "GrpcCost",
14
+ ]
@@ -0,0 +1,100 @@
1
+ """This module contains the abstract base class for cost strategies."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from enum import Enum
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from digitalkin.services.base_strategy import BaseStrategy
10
+
11
+
12
+ class CostType(Enum):
13
+ """Enum defining the types of costs that can be registered."""
14
+
15
+ OTHER = "OTHER"
16
+ TOKEN_INPUT = "TOKEN_INPUT"
17
+ TOKEN_OUTPUT = "TOKEN_OUTPUT"
18
+ API_CALL = "API_CALL"
19
+ STORAGE = "STORAGE"
20
+ TIME = "TIME"
21
+
22
+
23
+ class CostConfig(BaseModel):
24
+ """Pydantic model that defines a cost configuration.
25
+
26
+ :param cost_name: Name of the cost (unique identifier in the service).
27
+ :param cost_type: The type/category of the cost.
28
+ :param description: A short description of the cost.
29
+ :param unit: The unit of measurement (e.g. token, call, MB).
30
+ :param rate: The cost per unit (e.g. dollars per token).
31
+ """
32
+
33
+ cost_name: str
34
+ cost_type: Literal["TOKEN_INPUT", "TOKEN_OUTPUT", "API_CALL", "STORAGE", "TIME", "OTHER"]
35
+ description: str | None = None
36
+ unit: str
37
+ rate: float
38
+
39
+
40
+ class CostData(BaseModel):
41
+ """Data model for cost operations."""
42
+
43
+ cost: float
44
+ mission_id: str
45
+ name: str
46
+ cost_type: CostType
47
+ unit: str
48
+ rate: float
49
+ setup_version_id: str
50
+ quantity: float
51
+
52
+
53
+ class CostServiceError(Exception):
54
+ """Custom exception for CostService errors."""
55
+
56
+
57
+ class CostStrategy(BaseStrategy, ABC):
58
+ """Abstract base class for cost strategies."""
59
+
60
+ def __init__(
61
+ self,
62
+ mission_id: str,
63
+ setup_id: str,
64
+ setup_version_id: str,
65
+ config: dict[str, CostConfig],
66
+ ) -> None:
67
+ """Initialize the strategy.
68
+
69
+ Args:
70
+ mission_id: The ID of the mission this strategy is associated with
71
+ setup_id: The ID of the setup
72
+ setup_version_id: The ID of the setup version this strategy is associated with
73
+ config: Configuration dictionary for the strategy
74
+ """
75
+ super().__init__(mission_id, setup_id, setup_version_id)
76
+ self.config = config
77
+
78
+ @abstractmethod
79
+ def add(
80
+ self,
81
+ name: str,
82
+ cost_config_name: str,
83
+ quantity: float,
84
+ ) -> None:
85
+ """Register a new cost."""
86
+
87
+ @abstractmethod
88
+ def get(
89
+ self,
90
+ name: str,
91
+ ) -> list[CostData]:
92
+ """Get a cost."""
93
+
94
+ @abstractmethod
95
+ def get_filtered(
96
+ self,
97
+ names: list[str] | None = None,
98
+ cost_types: list[Literal["TOKEN_INPUT", "TOKEN_OUTPUT", "API_CALL", "STORAGE", "TIME", "OTHER"]] | None = None,
99
+ ) -> list[CostData]:
100
+ """Get filtered costs."""
@@ -0,0 +1,114 @@
1
+ """Default cost."""
2
+
3
+ from typing import Literal
4
+
5
+ from digitalkin.logger import logger
6
+ from digitalkin.services.cost.cost_strategy import (
7
+ CostConfig,
8
+ CostData,
9
+ CostServiceError,
10
+ CostStrategy,
11
+ CostType,
12
+ )
13
+
14
+
15
+ class DefaultCost(CostStrategy):
16
+ """Default cost strategy."""
17
+
18
+ def __init__(self, mission_id: str, setup_id: str, setup_version_id: str, config: dict[str, CostConfig]) -> None:
19
+ """Initialize the strategy.
20
+
21
+ Args:
22
+ mission_id: The ID of the mission this strategy is associated with
23
+ setup_id: The ID of the setup
24
+ setup_version_id: The ID of the setup version this strategy is associated with
25
+ config: The configuration dictionary for the cost
26
+ """
27
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
28
+ self.db: dict[str, list[CostData]] = {}
29
+
30
+ def add(
31
+ self,
32
+ name: str,
33
+ cost_config_name: str,
34
+ quantity: float,
35
+ ) -> None:
36
+ """Create a new record in the cost database.
37
+
38
+ Args:
39
+ name: The name of the cost
40
+ cost_config_name: The name of the cost config
41
+ quantity: The quantity of the cost
42
+
43
+ Raises:
44
+ CostServiceError: If the cost data is invalid or if the cost already exists
45
+ """
46
+ cost_config = self.config.get(cost_config_name)
47
+ if cost_config is None:
48
+ msg = f"Cost config {cost_config_name} not found in the configuration."
49
+ logger.error(msg)
50
+ raise CostServiceError(msg)
51
+ cost_data = CostData.model_validate({
52
+ "name": name,
53
+ "cost": cost_config.rate * quantity,
54
+ "unit": cost_config.unit,
55
+ "cost_type": getattr(CostType, cost_config.cost_type),
56
+ "mission_id": self.mission_id,
57
+ "rate": cost_config.rate,
58
+ "quantity": quantity,
59
+ "setup_version_id": self.setup_version_id,
60
+ })
61
+ if cost_data.mission_id not in self.db:
62
+ self.db[cost_data.mission_id] = []
63
+ if cost_data.name in [cost.name for cost in self.db[cost_data.mission_id]]:
64
+ msg = f"Cost with name {cost_data.name} already exists in mission {cost_data.mission_id}"
65
+ logger.error(msg)
66
+ raise CostServiceError(msg)
67
+ self.db[cost_data.mission_id].append(cost_data)
68
+
69
+ def get(self, name: str) -> list[CostData]:
70
+ """Get a record from the database.
71
+
72
+ Args:
73
+ name: The name of the cost
74
+
75
+ Returns:
76
+ list[CostData]: The cost data
77
+
78
+ Raises:
79
+ CostServiceError: If the cost data is invalid or if the cost does not exist
80
+ """
81
+ if self.mission_id not in self.db:
82
+ msg = f"Mission {self.mission_id} not found in the database."
83
+ logger.warning(msg)
84
+ raise CostServiceError(msg)
85
+
86
+ return [cost for cost in self.db[self.mission_id] if cost.name == name] or []
87
+
88
+ def get_filtered(
89
+ self,
90
+ names: list[str] | None = None,
91
+ cost_types: list[Literal["TOKEN_INPUT", "TOKEN_OUTPUT", "API_CALL", "STORAGE", "TIME", "OTHER"]] | None = None,
92
+ ) -> list[CostData]:
93
+ """Get records from the database.
94
+
95
+ Args:
96
+ names: The names of the costs
97
+ cost_types: The types of the costs
98
+
99
+ Returns:
100
+ list[CostData]: The list of records
101
+
102
+ Raises:
103
+ CostServiceError: If the cost data is invalid or if the cost does not exist
104
+ """
105
+ if self.mission_id not in self.db:
106
+ msg = f"Mission {self.mission_id} not found in the database."
107
+ logger.warning(msg)
108
+ raise CostServiceError(msg)
109
+
110
+ return [
111
+ cost
112
+ for cost in self.db[self.mission_id]
113
+ if (names and cost.name in names) or (cost_types and cost.cost_type in cost_types)
114
+ ]
@@ -0,0 +1,138 @@
1
+ """This module implements the gRPC Cost strategy."""
2
+
3
+ from typing import Literal
4
+
5
+ from agentic_mesh_protocol.cost.v1 import cost_pb2, cost_service_pb2_grpc
6
+ from google.protobuf import json_format
7
+
8
+ from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
9
+ from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
10
+ from digitalkin.logger import logger
11
+ from digitalkin.models.grpc_servers.models import ClientConfig
12
+ from digitalkin.services.cost.cost_strategy import (
13
+ CostConfig,
14
+ CostData,
15
+ CostServiceError,
16
+ CostStrategy,
17
+ CostType,
18
+ )
19
+
20
+
21
+ class GrpcCost(CostStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
22
+ """This class implements the default Cost strategy."""
23
+
24
+ def __init__(
25
+ self,
26
+ mission_id: str,
27
+ setup_id: str,
28
+ setup_version_id: str,
29
+ config: dict[str, CostConfig],
30
+ client_config: ClientConfig,
31
+ ) -> None:
32
+ """Initialize the cost."""
33
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
34
+ channel = self._init_channel(client_config)
35
+ self.stub = cost_service_pb2_grpc.CostServiceStub(channel)
36
+ logger.debug("Channel client 'Cost' initialized successfully")
37
+
38
+ def add(
39
+ self,
40
+ name: str,
41
+ cost_config_name: str,
42
+ quantity: float,
43
+ ) -> None:
44
+ """Create a new record in the cost database.
45
+
46
+ Args:
47
+ name: The name of the cost
48
+ cost_config_name: The name of the cost config
49
+ quantity: The quantity of the cost
50
+
51
+ Raises:
52
+ CostServiceError: If the cost config is invalid
53
+ """
54
+ with self.handle_grpc_errors("AddCost", CostServiceError):
55
+ cost_config = self.config.get(cost_config_name)
56
+ if cost_config is None:
57
+ msg = f"Cost config {cost_config_name} not found in the configuration."
58
+ logger.error(msg)
59
+ raise CostServiceError(msg)
60
+ valid_data = CostData.model_validate({
61
+ "name": name,
62
+ "cost": cost_config.rate * quantity,
63
+ "unit": cost_config.unit,
64
+ "cost_type": CostType[cost_config.cost_type],
65
+ "mission_id": self.mission_id,
66
+ "rate": cost_config.rate,
67
+ "quantity": quantity,
68
+ "setup_version_id": self.setup_version_id,
69
+ })
70
+ request = cost_pb2.AddCostRequest(
71
+ cost=valid_data.cost,
72
+ name=valid_data.name,
73
+ unit=valid_data.unit,
74
+ cost_type=valid_data.cost_type.name,
75
+ mission_id=valid_data.mission_id,
76
+ rate=valid_data.rate,
77
+ quantity=valid_data.quantity,
78
+ setup_version_id=valid_data.setup_version_id,
79
+ )
80
+ self.exec_grpc_query("AddCost", request)
81
+ logger.debug("Cost added with cost_dict: %s", valid_data.model_dump())
82
+
83
+ def get(self, name: str) -> list[CostData]:
84
+ """Get a record from the database.
85
+
86
+ Args:
87
+ name: The name of the cost
88
+
89
+ Returns:
90
+ CostData: The cost data
91
+ """
92
+ with self.handle_grpc_errors("GetCost", CostServiceError):
93
+ request = cost_pb2.GetCostRequest(name=name, mission_id=self.mission_id)
94
+ response: cost_pb2.GetCostResponse = self.exec_grpc_query("GetCost", request)
95
+ cost_data_list = [
96
+ json_format.MessageToDict(
97
+ cost,
98
+ preserving_proto_field_name=True,
99
+ always_print_fields_with_no_presence=True,
100
+ )
101
+ for cost in response.costs
102
+ ]
103
+ logger.debug("Costs retrieved with cost_dict: %s", cost_data_list)
104
+ return [CostData.model_validate(cost_data) for cost_data in cost_data_list]
105
+
106
+ def get_filtered(
107
+ self,
108
+ names: list[str] | None = None,
109
+ cost_types: list[Literal["TOKEN_INPUT", "TOKEN_OUTPUT", "API_CALL", "STORAGE", "TIME", "OTHER"]] | None = None,
110
+ ) -> list[CostData]:
111
+ """Get a list of records from the database.
112
+
113
+ Args:
114
+ names: The names of the costs
115
+ cost_types: The types of the costs
116
+
117
+ Returns:
118
+ list[CostData]: The cost data
119
+ """
120
+ with self.handle_grpc_errors("GetCosts", CostServiceError):
121
+ request = cost_pb2.GetCostsRequest(
122
+ mission_id=self.mission_id,
123
+ filter=cost_pb2.CostFilter(
124
+ names=names or [],
125
+ cost_types=cost_types or [],
126
+ ),
127
+ )
128
+ response: cost_pb2.GetCostsResponse = self.exec_grpc_query("GetCosts", request)
129
+ cost_data_list = [
130
+ json_format.MessageToDict(
131
+ cost,
132
+ preserving_proto_field_name=True,
133
+ always_print_fields_with_no_presence=True,
134
+ )
135
+ for cost in response.costs
136
+ ]
137
+ logger.debug("Filtered costs retrieved with cost_dict: %s", cost_data_list)
138
+ return [CostData.model_validate(cost_data) for cost_data in cost_data_list]
@@ -0,0 +1,7 @@
1
+ """This module is responsible for handling the filesystem services."""
2
+
3
+ from digitalkin.services.filesystem.default_filesystem import DefaultFilesystem
4
+ from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
5
+ from digitalkin.services.filesystem.grpc_filesystem import GrpcFilesystem
6
+
7
+ __all__ = ["DefaultFilesystem", "FilesystemStrategy", "GrpcFilesystem"]