digitalkin 0.3.0rc1__py3-none-any.whl → 0.3.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/__version__.py +1 -1
- digitalkin/core/common/__init__.py +9 -0
- digitalkin/core/common/factories.py +156 -0
- digitalkin/core/job_manager/base_job_manager.py +128 -28
- digitalkin/core/job_manager/single_job_manager.py +80 -25
- digitalkin/core/job_manager/taskiq_broker.py +114 -19
- digitalkin/core/job_manager/taskiq_job_manager.py +291 -39
- digitalkin/core/task_manager/base_task_manager.py +539 -0
- digitalkin/core/task_manager/local_task_manager.py +108 -0
- digitalkin/core/task_manager/remote_task_manager.py +87 -0
- digitalkin/core/task_manager/surrealdb_repository.py +43 -4
- digitalkin/core/task_manager/task_executor.py +249 -0
- digitalkin/core/task_manager/task_session.py +107 -19
- digitalkin/grpc_servers/module_server.py +2 -2
- digitalkin/grpc_servers/module_servicer.py +21 -12
- digitalkin/grpc_servers/registry_server.py +1 -1
- digitalkin/grpc_servers/registry_servicer.py +4 -4
- digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
- digitalkin/models/core/task_monitor.py +17 -0
- digitalkin/models/grpc_servers/models.py +4 -4
- digitalkin/models/module/module_context.py +5 -0
- digitalkin/models/module/module_types.py +304 -16
- digitalkin/modules/_base_module.py +66 -28
- digitalkin/services/cost/grpc_cost.py +8 -41
- digitalkin/services/filesystem/grpc_filesystem.py +9 -38
- digitalkin/services/services_config.py +11 -0
- digitalkin/services/services_models.py +3 -1
- digitalkin/services/setup/default_setup.py +5 -6
- digitalkin/services/setup/grpc_setup.py +51 -14
- digitalkin/services/storage/grpc_storage.py +2 -2
- digitalkin/services/user_profile/__init__.py +12 -0
- digitalkin/services/user_profile/default_user_profile.py +55 -0
- digitalkin/services/user_profile/grpc_user_profile.py +69 -0
- digitalkin/services/user_profile/user_profile_strategy.py +40 -0
- digitalkin/utils/__init__.py +28 -0
- digitalkin/utils/dynamic_schema.py +483 -0
- {digitalkin-0.3.0rc1.dist-info → digitalkin-0.3.1.dist-info}/METADATA +9 -29
- {digitalkin-0.3.0rc1.dist-info → digitalkin-0.3.1.dist-info}/RECORD +42 -30
- modules/dynamic_setup_module.py +362 -0
- digitalkin/core/task_manager/task_manager.py +0 -439
- {digitalkin-0.3.0rc1.dist-info → digitalkin-0.3.1.dist-info}/WHEEL +0 -0
- {digitalkin-0.3.0rc1.dist-info → digitalkin-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {digitalkin-0.3.0rc1.dist-info → digitalkin-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -107,14 +107,18 @@ class BaseModule( # noqa: PLR0904
|
|
|
107
107
|
return self._status
|
|
108
108
|
|
|
109
109
|
@classmethod
|
|
110
|
-
def get_secret_format(cls, *, llm_format: bool) -> str:
|
|
110
|
+
async def get_secret_format(cls, *, llm_format: bool) -> str:
|
|
111
111
|
"""Get the JSON schema of the secret format model.
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
Args:
|
|
114
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
115
|
+
references and simplified structure.
|
|
115
116
|
|
|
116
117
|
Returns:
|
|
117
|
-
The JSON schema of the secret format as a string.
|
|
118
|
+
The JSON schema of the secret format as a JSON string.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
NotImplementedError: If the `secret_format` class attribute is not defined.
|
|
118
122
|
"""
|
|
119
123
|
if cls.secret_format is not None:
|
|
120
124
|
if llm_format:
|
|
@@ -124,14 +128,18 @@ class BaseModule( # noqa: PLR0904
|
|
|
124
128
|
raise NotImplementedError(msg)
|
|
125
129
|
|
|
126
130
|
@classmethod
|
|
127
|
-
def get_input_format(cls, *, llm_format: bool) -> str:
|
|
131
|
+
async def get_input_format(cls, *, llm_format: bool) -> str:
|
|
128
132
|
"""Get the JSON schema of the input format model.
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
Args:
|
|
135
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
136
|
+
references and simplified structure.
|
|
132
137
|
|
|
133
138
|
Returns:
|
|
134
|
-
The JSON schema of the input format as a string.
|
|
139
|
+
The JSON schema of the input format as a JSON string.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
NotImplementedError: If the `input_format` class attribute is not defined.
|
|
135
143
|
"""
|
|
136
144
|
if cls.input_format is not None:
|
|
137
145
|
if llm_format:
|
|
@@ -141,14 +149,18 @@ class BaseModule( # noqa: PLR0904
|
|
|
141
149
|
raise NotImplementedError(msg)
|
|
142
150
|
|
|
143
151
|
@classmethod
|
|
144
|
-
def get_output_format(cls, *, llm_format: bool) -> str:
|
|
152
|
+
async def get_output_format(cls, *, llm_format: bool) -> str:
|
|
145
153
|
"""Get the JSON schema of the output format model.
|
|
146
154
|
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
Args:
|
|
156
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
157
|
+
references and simplified structure.
|
|
149
158
|
|
|
150
159
|
Returns:
|
|
151
|
-
The JSON schema of the output format as a string.
|
|
160
|
+
The JSON schema of the output format as a JSON string.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
NotImplementedError: If the `output_format` class attribute is not defined.
|
|
152
164
|
"""
|
|
153
165
|
if cls.output_format is not None:
|
|
154
166
|
if llm_format:
|
|
@@ -158,20 +170,29 @@ class BaseModule( # noqa: PLR0904
|
|
|
158
170
|
raise NotImplementedError(msg)
|
|
159
171
|
|
|
160
172
|
@classmethod
|
|
161
|
-
def get_config_setup_format(cls, *, llm_format: bool) -> str:
|
|
173
|
+
async def get_config_setup_format(cls, *, llm_format: bool) -> str:
|
|
162
174
|
"""Gets the JSON schema of the config setup format model.
|
|
163
175
|
|
|
164
|
-
The config setup format is used only to initialize the module with configuration
|
|
165
|
-
|
|
176
|
+
The config setup format is used only to initialize the module with configuration
|
|
177
|
+
data. It includes fields marked with `json_schema_extra={"config": True}` and
|
|
178
|
+
excludes hidden runtime fields.
|
|
166
179
|
|
|
167
|
-
|
|
168
|
-
|
|
180
|
+
Dynamic schema fields are always resolved when generating the schema, as this
|
|
181
|
+
method is typically called during module discovery or schema generation where
|
|
182
|
+
fresh values are needed.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
186
|
+
references and simplified structure.
|
|
169
187
|
|
|
170
188
|
Returns:
|
|
171
|
-
The JSON schema of the config setup format as a string.
|
|
189
|
+
The JSON schema of the config setup format as a JSON string.
|
|
190
|
+
|
|
191
|
+
Raises:
|
|
192
|
+
NotImplementedError: If the `setup_format` class attribute is not defined.
|
|
172
193
|
"""
|
|
173
194
|
if cls.setup_format is not None:
|
|
174
|
-
setup_format = cls.setup_format.get_clean_model(config_fields=True, hidden_fields=False)
|
|
195
|
+
setup_format = await cls.setup_format.get_clean_model(config_fields=True, hidden_fields=False, force=True)
|
|
175
196
|
if llm_format:
|
|
176
197
|
return json.dumps(llm_ready_schema(setup_format), indent=2)
|
|
177
198
|
return json.dumps(setup_format.model_json_schema(), indent=2)
|
|
@@ -179,17 +200,28 @@ class BaseModule( # noqa: PLR0904
|
|
|
179
200
|
raise NotImplementedError(msg)
|
|
180
201
|
|
|
181
202
|
@classmethod
|
|
182
|
-
def get_setup_format(cls, *, llm_format: bool) -> str:
|
|
203
|
+
async def get_setup_format(cls, *, llm_format: bool) -> str:
|
|
183
204
|
"""Gets the JSON schema of the setup format model.
|
|
184
205
|
|
|
185
|
-
|
|
186
|
-
|
|
206
|
+
The setup format is used at runtime and includes hidden fields but excludes
|
|
207
|
+
config-only fields. This is the schema used when running the module.
|
|
208
|
+
|
|
209
|
+
Dynamic schema fields are always resolved when generating the schema, as this
|
|
210
|
+
method is typically called during module discovery or schema generation where
|
|
211
|
+
fresh values are needed.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
llm_format: If True, return LLM-optimized schema format with inlined
|
|
215
|
+
references and simplified structure.
|
|
187
216
|
|
|
188
217
|
Returns:
|
|
189
|
-
The JSON schema of the setup format as a string.
|
|
218
|
+
The JSON schema of the setup format as a JSON string.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
NotImplementedError: If the `setup_format` class attribute is not defined.
|
|
190
222
|
"""
|
|
191
223
|
if cls.setup_format is not None:
|
|
192
|
-
setup_format = cls.setup_format.get_clean_model(config_fields=False, hidden_fields=True)
|
|
224
|
+
setup_format = await cls.setup_format.get_clean_model(config_fields=False, hidden_fields=True, force=True)
|
|
193
225
|
if llm_format:
|
|
194
226
|
return json.dumps(llm_ready_schema(setup_format), indent=2)
|
|
195
227
|
return json.dumps(setup_format.model_json_schema(), indent=2)
|
|
@@ -221,17 +253,22 @@ class BaseModule( # noqa: PLR0904
|
|
|
221
253
|
return cls.input_format(**input_data)
|
|
222
254
|
|
|
223
255
|
@classmethod
|
|
224
|
-
def create_setup_model(cls, setup_data: dict[str, Any], *, config_fields: bool = False) -> SetupModelT:
|
|
256
|
+
async def create_setup_model(cls, setup_data: dict[str, Any], *, config_fields: bool = False) -> SetupModelT:
|
|
225
257
|
"""Create the setup model from the setup data.
|
|
226
258
|
|
|
259
|
+
Creates a filtered setup model instance based on the provided data.
|
|
260
|
+
Uses `get_clean_model()` internally to get the appropriate model class
|
|
261
|
+
with field filtering applied.
|
|
262
|
+
|
|
227
263
|
Args:
|
|
228
264
|
setup_data: The setup data to create the model from.
|
|
229
265
|
config_fields: If True, include only fields with json_schema_extra["config"] == True.
|
|
230
266
|
|
|
231
267
|
Returns:
|
|
232
|
-
|
|
268
|
+
An instance of the setup model with the provided data.
|
|
233
269
|
"""
|
|
234
|
-
|
|
270
|
+
model_cls = await cls.setup_format.get_clean_model(config_fields=config_fields, hidden_fields=True)
|
|
271
|
+
return model_cls(**setup_data)
|
|
235
272
|
|
|
236
273
|
@classmethod
|
|
237
274
|
def create_secret_model(cls, secret_data: dict[str, Any]) -> SecretModelT:
|
|
@@ -436,7 +473,8 @@ class BaseModule( # noqa: PLR0904
|
|
|
436
473
|
|
|
437
474
|
wrapper = config_setup_data.model_dump()
|
|
438
475
|
wrapper["content"] = content.model_dump()
|
|
439
|
-
await
|
|
476
|
+
setup_model = await self.create_setup_model(wrapper)
|
|
477
|
+
await callback(setup_model)
|
|
440
478
|
self._status = ModuleStatus.STOPPING
|
|
441
479
|
except Exception:
|
|
442
480
|
logger.error("Error during module lifecyle")
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"""This module implements the gRPC Cost strategy."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from contextlib import contextmanager
|
|
5
|
-
from typing import Any, Literal
|
|
3
|
+
from typing import Literal
|
|
6
4
|
|
|
7
|
-
from digitalkin_proto.
|
|
5
|
+
from digitalkin_proto.agentic_mesh_protocol.cost.v1 import cost_pb2, cost_service_pb2_grpc
|
|
8
6
|
from google.protobuf import json_format
|
|
9
7
|
|
|
10
|
-
from digitalkin.grpc_servers.utils.exceptions import ServerError
|
|
11
8
|
from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
|
|
9
|
+
from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
|
|
12
10
|
from digitalkin.logger import logger
|
|
13
11
|
from digitalkin.models.grpc_servers.models import ClientConfig
|
|
14
12
|
from digitalkin.services.cost.cost_strategy import (
|
|
@@ -20,40 +18,9 @@ from digitalkin.services.cost.cost_strategy import (
|
|
|
20
18
|
)
|
|
21
19
|
|
|
22
20
|
|
|
23
|
-
class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
21
|
+
class GrpcCost(CostStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
|
|
24
22
|
"""This class implements the default Cost strategy."""
|
|
25
23
|
|
|
26
|
-
@staticmethod
|
|
27
|
-
@contextmanager
|
|
28
|
-
def _handle_grpc_errors(operation: str) -> Generator[Any, Any, Any]:
|
|
29
|
-
"""Context manager for consistent gRPC error handling.
|
|
30
|
-
|
|
31
|
-
Yields:
|
|
32
|
-
Allow error handling in context.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
operation: Description of the operation being performed.
|
|
36
|
-
|
|
37
|
-
Raises:
|
|
38
|
-
ValueError: Error with the model validation.
|
|
39
|
-
ServerError: from gRPC Client.
|
|
40
|
-
CostServiceError: Unexpected error.
|
|
41
|
-
"""
|
|
42
|
-
try:
|
|
43
|
-
yield
|
|
44
|
-
except CostServiceError as e:
|
|
45
|
-
msg = f"CostServiceError in {operation}: {e}"
|
|
46
|
-
logger.exception(msg)
|
|
47
|
-
raise CostServiceError(msg) from e
|
|
48
|
-
except ServerError as e:
|
|
49
|
-
msg = f"gRPC {operation} failed: {e}"
|
|
50
|
-
logger.exception(msg)
|
|
51
|
-
raise ServerError(msg) from e
|
|
52
|
-
except Exception as e:
|
|
53
|
-
msg = f"Unexpected error in {operation}"
|
|
54
|
-
logger.exception(msg)
|
|
55
|
-
raise CostServiceError(msg) from e
|
|
56
|
-
|
|
57
24
|
def __init__(
|
|
58
25
|
self,
|
|
59
26
|
mission_id: str,
|
|
@@ -66,7 +33,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
|
66
33
|
super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id, config=config)
|
|
67
34
|
channel = self._init_channel(client_config)
|
|
68
35
|
self.stub = cost_service_pb2_grpc.CostServiceStub(channel)
|
|
69
|
-
logger.debug("Channel client 'Cost' initialized
|
|
36
|
+
logger.debug("Channel client 'Cost' initialized successfully")
|
|
70
37
|
|
|
71
38
|
def add(
|
|
72
39
|
self,
|
|
@@ -84,7 +51,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
|
84
51
|
Raises:
|
|
85
52
|
CostServiceError: If the cost config is invalid
|
|
86
53
|
"""
|
|
87
|
-
with self.
|
|
54
|
+
with self.handle_grpc_errors("AddCost", CostServiceError):
|
|
88
55
|
cost_config = self.config.get(cost_config_name)
|
|
89
56
|
if cost_config is None:
|
|
90
57
|
msg = f"Cost config {cost_config_name} not found in the configuration."
|
|
@@ -122,7 +89,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
|
122
89
|
Returns:
|
|
123
90
|
CostData: The cost data
|
|
124
91
|
"""
|
|
125
|
-
with self.
|
|
92
|
+
with self.handle_grpc_errors("GetCost", CostServiceError):
|
|
126
93
|
request = cost_pb2.GetCostRequest(name=name, mission_id=self.mission_id)
|
|
127
94
|
response: cost_pb2.GetCostResponse = self.exec_grpc_query("GetCost", request)
|
|
128
95
|
cost_data_list = [
|
|
@@ -150,7 +117,7 @@ class GrpcCost(CostStrategy, GrpcClientWrapper):
|
|
|
150
117
|
Returns:
|
|
151
118
|
list[CostData]: The cost data
|
|
152
119
|
"""
|
|
153
|
-
with self.
|
|
120
|
+
with self.handle_grpc_errors("GetCosts", CostServiceError):
|
|
154
121
|
request = cost_pb2.GetCostsRequest(
|
|
155
122
|
mission_id=self.mission_id,
|
|
156
123
|
filter=cost_pb2.CostFilter(
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
"""gRPC filesystem implementation."""
|
|
2
2
|
|
|
3
|
-
from collections.abc import Generator
|
|
4
|
-
from contextlib import contextmanager
|
|
5
3
|
from typing import Any, Literal
|
|
6
4
|
|
|
7
|
-
from digitalkin_proto.
|
|
5
|
+
from digitalkin_proto.agentic_mesh_protocol.filesystem.v1 import filesystem_pb2, filesystem_service_pb2_grpc
|
|
8
6
|
from google.protobuf import struct_pb2
|
|
9
7
|
from google.protobuf.json_format import MessageToDict
|
|
10
8
|
|
|
11
|
-
from digitalkin.grpc_servers.utils.exceptions import ServerError
|
|
12
9
|
from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
|
|
10
|
+
from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
|
|
13
11
|
from digitalkin.logger import logger
|
|
14
12
|
from digitalkin.models.grpc_servers.models import ClientConfig
|
|
15
13
|
from digitalkin.services.filesystem.filesystem_strategy import (
|
|
@@ -21,36 +19,9 @@ from digitalkin.services.filesystem.filesystem_strategy import (
|
|
|
21
19
|
)
|
|
22
20
|
|
|
23
21
|
|
|
24
|
-
class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
|
|
22
|
+
class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
|
|
25
23
|
"""Default state filesystem strategy."""
|
|
26
24
|
|
|
27
|
-
@staticmethod
|
|
28
|
-
@contextmanager
|
|
29
|
-
def _handle_grpc_errors(operation: str) -> Generator[Any, Any, Any]:
|
|
30
|
-
"""Context manager for consistent gRPC error handling.
|
|
31
|
-
|
|
32
|
-
Yields:
|
|
33
|
-
Allow error handling in context.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
operation: Description of the operation being performed.
|
|
37
|
-
|
|
38
|
-
Raises:
|
|
39
|
-
ValueError: Error with the model validation.
|
|
40
|
-
ServerError: from gRPC Client.
|
|
41
|
-
FilesystemServiceError: Filesystem service internal.
|
|
42
|
-
"""
|
|
43
|
-
try:
|
|
44
|
-
yield
|
|
45
|
-
except ServerError as e:
|
|
46
|
-
msg = f"gRPC {operation} failed: {e}"
|
|
47
|
-
logger.exception(msg)
|
|
48
|
-
raise ServerError(msg) from e
|
|
49
|
-
except Exception as e:
|
|
50
|
-
msg = f"Unexpected error in {operation}"
|
|
51
|
-
logger.exception(msg)
|
|
52
|
-
raise FilesystemServiceError(msg) from e
|
|
53
|
-
|
|
54
25
|
@staticmethod
|
|
55
26
|
def _file_type_to_enum(file_type: str) -> filesystem_pb2.FileType:
|
|
56
27
|
"""Convert a file type string to a FileType enum.
|
|
@@ -148,7 +119,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
|
|
|
148
119
|
self.service_name = "FilesystemService"
|
|
149
120
|
channel = self._init_channel(client_config)
|
|
150
121
|
self.stub = filesystem_service_pb2_grpc.FilesystemServiceStub(channel)
|
|
151
|
-
logger.debug("Channel client 'Filesystem' initialized
|
|
122
|
+
logger.debug("Channel client 'Filesystem' initialized successfully")
|
|
152
123
|
|
|
153
124
|
def upload_files(
|
|
154
125
|
self,
|
|
@@ -163,7 +134,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
|
|
|
163
134
|
tuple[list[FilesystemRecord], int, int]: List of uploaded files, total uploaded count, total failed count
|
|
164
135
|
"""
|
|
165
136
|
logger.debug("Uploading %d files", len(files))
|
|
166
|
-
with
|
|
137
|
+
with self.handle_grpc_errors("UploadFiles", FilesystemServiceError):
|
|
167
138
|
upload_files: list[filesystem_pb2.UploadFileData] = []
|
|
168
139
|
for file in files:
|
|
169
140
|
metadata_struct: struct_pb2.Struct | None = None
|
|
@@ -213,7 +184,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
|
|
|
213
184
|
context_id = self.setup_id
|
|
214
185
|
case "mission":
|
|
215
186
|
context_id = self.mission_id
|
|
216
|
-
with
|
|
187
|
+
with self.handle_grpc_errors("GetFile", FilesystemServiceError):
|
|
217
188
|
request = filesystem_pb2.GetFileRequest(
|
|
218
189
|
context=context_id,
|
|
219
190
|
file_id=file_id,
|
|
@@ -261,7 +232,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
|
|
|
261
232
|
Raises:
|
|
262
233
|
FilesystemServiceError: If there is an error during update
|
|
263
234
|
"""
|
|
264
|
-
with
|
|
235
|
+
with self.handle_grpc_errors("UpdateFile", FilesystemServiceError):
|
|
265
236
|
request = filesystem_pb2.UpdateFileRequest(
|
|
266
237
|
context=self.mission_id,
|
|
267
238
|
file_id=file_id,
|
|
@@ -295,7 +266,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
|
|
|
295
266
|
Returns:
|
|
296
267
|
tuple[dict[str, bool], int, int]: Results per file, total deleted count, total failed count
|
|
297
268
|
"""
|
|
298
|
-
with
|
|
269
|
+
with self.handle_grpc_errors("DeleteFiles", FilesystemServiceError):
|
|
299
270
|
request = filesystem_pb2.DeleteFilesRequest(
|
|
300
271
|
context=self.mission_id,
|
|
301
272
|
filters=self._filter_to_proto(filters),
|
|
@@ -332,7 +303,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
|
|
|
332
303
|
context_id = self.setup_id
|
|
333
304
|
case "mission":
|
|
334
305
|
context_id = self.mission_id
|
|
335
|
-
with
|
|
306
|
+
with self.handle_grpc_errors("GetFiles", FilesystemServiceError):
|
|
336
307
|
request = filesystem_pb2.GetFilesRequest(
|
|
337
308
|
context=context_id,
|
|
338
309
|
filters=self._filter_to_proto(filters),
|
|
@@ -12,6 +12,7 @@ from digitalkin.services.registry import DefaultRegistry, RegistryStrategy
|
|
|
12
12
|
from digitalkin.services.services_models import ServicesMode, ServicesStrategy
|
|
13
13
|
from digitalkin.services.snapshot import DefaultSnapshot, SnapshotStrategy
|
|
14
14
|
from digitalkin.services.storage import DefaultStorage, GrpcStorage, StorageStrategy
|
|
15
|
+
from digitalkin.services.user_profile import DefaultUserProfile, GrpcUserProfile, UserProfileStrategy
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class ServicesConfig(BaseModel):
|
|
@@ -53,6 +54,10 @@ class ServicesConfig(BaseModel):
|
|
|
53
54
|
default_factory=lambda: ServicesStrategy(local=DefaultIdentity, remote=DefaultIdentity)
|
|
54
55
|
)
|
|
55
56
|
_config_identity: dict[str, Any | None] = PrivateAttr(default_factory=dict)
|
|
57
|
+
_user_profile: ServicesStrategy[UserProfileStrategy] = PrivateAttr(
|
|
58
|
+
default_factory=lambda: ServicesStrategy(local=DefaultUserProfile, remote=GrpcUserProfile)
|
|
59
|
+
)
|
|
60
|
+
_config_user_profile: dict[str, Any | None] = PrivateAttr(default_factory=dict)
|
|
56
61
|
|
|
57
62
|
# List of valid strategy names for validation
|
|
58
63
|
_valid_strategy_names: ClassVar[set[str]] = {
|
|
@@ -63,6 +68,7 @@ class ServicesConfig(BaseModel):
|
|
|
63
68
|
"filesystem",
|
|
64
69
|
"agent",
|
|
65
70
|
"identity",
|
|
71
|
+
"user_profile",
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
def __init__(
|
|
@@ -169,6 +175,11 @@ class ServicesConfig(BaseModel):
|
|
|
169
175
|
"""Get the identity service strategy class based on the current mode."""
|
|
170
176
|
return self._identity[self.mode.value]
|
|
171
177
|
|
|
178
|
+
@property
|
|
179
|
+
def user_profile(self) -> type[UserProfileStrategy]:
|
|
180
|
+
"""Get the user_profile service strategy class based on the current mode."""
|
|
181
|
+
return self._user_profile[self.mode.value]
|
|
182
|
+
|
|
172
183
|
def update_mode(self, mode: ServicesMode) -> None:
|
|
173
184
|
"""Update the strategy mode.
|
|
174
185
|
|
|
@@ -13,6 +13,7 @@ from digitalkin.services.identity import IdentityStrategy
|
|
|
13
13
|
from digitalkin.services.registry import RegistryStrategy
|
|
14
14
|
from digitalkin.services.snapshot import SnapshotStrategy
|
|
15
15
|
from digitalkin.services.storage import StorageStrategy
|
|
16
|
+
from digitalkin.services.user_profile import UserProfileStrategy
|
|
16
17
|
|
|
17
18
|
# Define type variables
|
|
18
19
|
T = TypeVar(
|
|
@@ -23,7 +24,8 @@ T = TypeVar(
|
|
|
23
24
|
| IdentityStrategy
|
|
24
25
|
| RegistryStrategy
|
|
25
26
|
| SnapshotStrategy
|
|
26
|
-
| StorageStrategy
|
|
27
|
+
| StorageStrategy
|
|
28
|
+
| UserProfileStrategy,
|
|
27
29
|
)
|
|
28
30
|
|
|
29
31
|
|
|
@@ -7,8 +7,7 @@ from typing import Any
|
|
|
7
7
|
from pydantic import ValidationError
|
|
8
8
|
|
|
9
9
|
from digitalkin.logger import logger
|
|
10
|
-
from digitalkin.services.setup.
|
|
11
|
-
from digitalkin.services.setup.setup_strategy import SetupServiceError, SetupStrategy
|
|
10
|
+
from digitalkin.services.setup.setup_strategy import SetupData, SetupServiceError, SetupStrategy, SetupVersionData
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class DefaultSetup(SetupStrategy):
|
|
@@ -47,7 +46,7 @@ class DefaultSetup(SetupStrategy):
|
|
|
47
46
|
)
|
|
48
47
|
valid_data.id = setup_id
|
|
49
48
|
self.setups[setup_id] = valid_data
|
|
50
|
-
logger.debug("CREATE SETUP DATA %s:%s
|
|
49
|
+
logger.debug("CREATE SETUP DATA %s:%s successful", setup_id, valid_data)
|
|
51
50
|
return setup_id
|
|
52
51
|
|
|
53
52
|
def get_setup(self, setup_dict: dict[str, Any]) -> SetupData:
|
|
@@ -131,7 +130,7 @@ class DefaultSetup(SetupStrategy):
|
|
|
131
130
|
if setup_version_dict["setup_id"] not in self.setup_versions:
|
|
132
131
|
self.setup_versions[setup_version_dict["setup_id"]] = {}
|
|
133
132
|
self.setup_versions[setup_version_dict["setup_id"]][valid_data.version] = valid_data
|
|
134
|
-
logger.debug("CREATE SETUP VERSION DATA %s:%s
|
|
133
|
+
logger.debug("CREATE SETUP VERSION DATA %s:%s successful", setup_version_dict["setup_id"], valid_data)
|
|
135
134
|
return valid_data.version
|
|
136
135
|
|
|
137
136
|
def get_setup_version(self, setup_version_dict: dict[str, Any]) -> SetupVersionData:
|
|
@@ -173,7 +172,7 @@ class DefaultSetup(SetupStrategy):
|
|
|
173
172
|
|
|
174
173
|
return [
|
|
175
174
|
value
|
|
176
|
-
for value in setup_version_dict["setup_id"].values()
|
|
175
|
+
for value in self.setup_versions[setup_version_dict["setup_id"]].values()
|
|
177
176
|
if setup_version_dict["query_versions"] in value.version or setup_version_dict["name"] in value.name
|
|
178
177
|
]
|
|
179
178
|
|
|
@@ -190,7 +189,7 @@ class DefaultSetup(SetupStrategy):
|
|
|
190
189
|
logger.debug("UPDATE setup_id = %s: setup_id DOESN'T EXIST", setup_version_dict["setup_id"])
|
|
191
190
|
return False
|
|
192
191
|
|
|
193
|
-
if setup_version_dict["version"] not in self.setup_versions["setup_id"]:
|
|
192
|
+
if setup_version_dict["version"] not in self.setup_versions[setup_version_dict["setup_id"]]:
|
|
194
193
|
logger.debug("UPDATE setup_id = %s: setup_id DOESN'T EXIST", setup_version_dict["setup_id"])
|
|
195
194
|
return False
|
|
196
195
|
|
|
@@ -5,7 +5,7 @@ from contextlib import contextmanager
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
import grpc
|
|
8
|
-
from digitalkin_proto.
|
|
8
|
+
from digitalkin_proto.agentic_mesh_protocol.setup.v1 import (
|
|
9
9
|
setup_pb2,
|
|
10
10
|
setup_service_pb2_grpc,
|
|
11
11
|
)
|
|
@@ -30,10 +30,10 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
30
30
|
"""
|
|
31
31
|
channel = self._init_channel(config)
|
|
32
32
|
self.stub = setup_service_pb2_grpc.SetupServiceStub(channel)
|
|
33
|
-
logger.debug("Channel client 'setup' initialized
|
|
33
|
+
logger.debug("Channel client 'setup' initialized successfully")
|
|
34
34
|
|
|
35
35
|
@contextmanager
|
|
36
|
-
def
|
|
36
|
+
def handle_grpc_errors(self, operation: str) -> Generator[Any, Any, Any]: # noqa: PLR6301
|
|
37
37
|
"""Context manager for consistent gRPC error handling.
|
|
38
38
|
|
|
39
39
|
Yields:
|
|
@@ -76,7 +76,7 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
76
76
|
ServerError: If gRPC operation fails.
|
|
77
77
|
SetupServiceError: For any unexpected internal error.
|
|
78
78
|
"""
|
|
79
|
-
with self.
|
|
79
|
+
with self.handle_grpc_errors("Setup Creation"):
|
|
80
80
|
valid_data = SetupData.model_validate(setup_dict)
|
|
81
81
|
|
|
82
82
|
request = setup_pb2.CreateSetupRequest(
|
|
@@ -104,7 +104,7 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
104
104
|
ServerError: If gRPC operation fails.
|
|
105
105
|
SetupServiceError: For any unexpected internal error.
|
|
106
106
|
"""
|
|
107
|
-
with self.
|
|
107
|
+
with self.handle_grpc_errors("Get Setup"):
|
|
108
108
|
if "setup_id" not in setup_dict:
|
|
109
109
|
msg = "Setup name is required"
|
|
110
110
|
raise ValidationError(msg)
|
|
@@ -132,7 +132,7 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
132
132
|
"""
|
|
133
133
|
current_setup_version = None
|
|
134
134
|
|
|
135
|
-
with self.
|
|
135
|
+
with self.handle_grpc_errors("Setup Update"):
|
|
136
136
|
valid_data = SetupData.model_validate(setup_dict)
|
|
137
137
|
|
|
138
138
|
if valid_data.current_setup_version is not None:
|
|
@@ -162,7 +162,7 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
162
162
|
ServerError: If gRPC operation fails.
|
|
163
163
|
SetupServiceError: For any unexpected internal error.
|
|
164
164
|
"""
|
|
165
|
-
with self.
|
|
165
|
+
with self.handle_grpc_errors("Setup Deletion"):
|
|
166
166
|
setup_id = setup_dict.get("setup_id")
|
|
167
167
|
if not setup_id:
|
|
168
168
|
msg = "Setup name is required for deletion"
|
|
@@ -186,7 +186,7 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
186
186
|
ServerError: If gRPC operation fails.
|
|
187
187
|
SetupServiceError: For any unexpected internal error.
|
|
188
188
|
"""
|
|
189
|
-
with self.
|
|
189
|
+
with self.handle_grpc_errors("Setup Version Creation"):
|
|
190
190
|
valid_data = SetupVersionData.model_validate(setup_version_dict)
|
|
191
191
|
content_struct = Struct()
|
|
192
192
|
content_struct.update(valid_data.content)
|
|
@@ -216,14 +216,16 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
216
216
|
ServerError: If gRPC operation fails.
|
|
217
217
|
SetupServiceError: For any unexpected internal error.
|
|
218
218
|
"""
|
|
219
|
-
with self.
|
|
219
|
+
with self.handle_grpc_errors("Get Setup Version"):
|
|
220
220
|
setup_version_id = setup_version_dict.get("setup_version_id")
|
|
221
221
|
if not setup_version_id:
|
|
222
222
|
msg = "Setup version id is required"
|
|
223
223
|
raise ValidationError(msg)
|
|
224
224
|
request = setup_pb2.GetSetupVersionRequest(setup_version_id=setup_version_id)
|
|
225
225
|
response = self.exec_grpc_query("GetSetupVersion", request)
|
|
226
|
-
return SetupVersionData(
|
|
226
|
+
return SetupVersionData(
|
|
227
|
+
**json_format.MessageToDict(response.setup_version, preserving_proto_field_name=True)
|
|
228
|
+
)
|
|
227
229
|
|
|
228
230
|
def search_setup_versions(self, setup_version_dict: dict[str, Any]) -> list[SetupVersionData]:
|
|
229
231
|
"""Search for setup versions based on filters.
|
|
@@ -239,7 +241,7 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
239
241
|
SetupServiceError: For any unexpected internal error.
|
|
240
242
|
ValidationError: If both name and version are not provided.
|
|
241
243
|
"""
|
|
242
|
-
with self.
|
|
244
|
+
with self.handle_grpc_errors("Search Setup Versions"):
|
|
243
245
|
if "name" not in setup_version_dict and "version" not in setup_version_dict:
|
|
244
246
|
msg = "Either name or version must be provided"
|
|
245
247
|
raise ValidationError(msg)
|
|
@@ -248,7 +250,10 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
248
250
|
version=setup_version_dict.get("version", ""),
|
|
249
251
|
)
|
|
250
252
|
response = self.exec_grpc_query("SearchSetupVersions", request)
|
|
251
|
-
return [
|
|
253
|
+
return [
|
|
254
|
+
SetupVersionData(**json_format.MessageToDict(sv, preserving_proto_field_name=True))
|
|
255
|
+
for sv in response.setup_versions
|
|
256
|
+
]
|
|
252
257
|
|
|
253
258
|
def update_setup_version(self, setup_version_dict: dict[str, Any]) -> bool:
|
|
254
259
|
"""Update an existing setup version.
|
|
@@ -264,7 +269,7 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
264
269
|
ServerError: If gRPC operation fails.
|
|
265
270
|
SetupServiceError: For any unexpected internal error.
|
|
266
271
|
"""
|
|
267
|
-
with self.
|
|
272
|
+
with self.handle_grpc_errors("Setup Version Update"):
|
|
268
273
|
valid_data = SetupVersionData.model_validate(setup_version_dict)
|
|
269
274
|
content_struct = Struct()
|
|
270
275
|
content_struct.update(valid_data.content)
|
|
@@ -295,7 +300,7 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
295
300
|
ServerError: If gRPC operation fails.
|
|
296
301
|
SetupServiceError: For any unexpected internal error.
|
|
297
302
|
"""
|
|
298
|
-
with self.
|
|
303
|
+
with self.handle_grpc_errors("Setup Version Deletion"):
|
|
299
304
|
setup_version_id = setup_version_dict.get("setup_version_id")
|
|
300
305
|
if not setup_version_id:
|
|
301
306
|
msg = "Setup version id is required for deletion"
|
|
@@ -304,3 +309,35 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
|
|
|
304
309
|
response = self.exec_grpc_query("DeleteSetupVersion", request)
|
|
305
310
|
logger.debug("Setup Version '%s' query sent successfully", setup_version_id)
|
|
306
311
|
return getattr(response, "success", False)
|
|
312
|
+
|
|
313
|
+
def list_setups(self, list_dict: dict[str, Any]) -> dict[str, Any]:
|
|
314
|
+
"""List setups with optional filtering and pagination.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
list_dict: Dictionary with optional filters:
|
|
318
|
+
- organisation_id: Filter by organisation
|
|
319
|
+
- owner_id: Filter by owner
|
|
320
|
+
- limit: Maximum number of results
|
|
321
|
+
- offset: Number of results to skip
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
dict[str, Any]: Dictionary with 'setups' list and 'total_count'.
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
ServerError: If gRPC operation fails.
|
|
328
|
+
SetupServiceError: For any unexpected internal error.
|
|
329
|
+
"""
|
|
330
|
+
with self.handle_grpc_errors("List Setups"):
|
|
331
|
+
request = setup_pb2.ListSetupsRequest(
|
|
332
|
+
organisation_id=list_dict.get("organisation_id", ""),
|
|
333
|
+
owner_id=list_dict.get("owner_id", ""),
|
|
334
|
+
limit=list_dict.get("limit", 0),
|
|
335
|
+
offset=list_dict.get("offset", 0),
|
|
336
|
+
)
|
|
337
|
+
response = self.exec_grpc_query("ListSetups", request)
|
|
338
|
+
return {
|
|
339
|
+
"setups": [
|
|
340
|
+
json_format.MessageToDict(setup, preserving_proto_field_name=True) for setup in response.setups
|
|
341
|
+
],
|
|
342
|
+
"total_count": response.total_count,
|
|
343
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""This module implements the default storage strategy."""
|
|
2
2
|
|
|
3
|
-
from digitalkin_proto.
|
|
3
|
+
from digitalkin_proto.agentic_mesh_protocol.storage.v1 import data_pb2, storage_service_pb2_grpc
|
|
4
4
|
from google.protobuf import json_format
|
|
5
5
|
from google.protobuf.struct_pb2 import Struct
|
|
6
6
|
from pydantic import BaseModel
|
|
@@ -211,4 +211,4 @@ class GrpcStorage(StorageStrategy, GrpcClientWrapper):
|
|
|
211
211
|
|
|
212
212
|
channel = self._init_channel(client_config)
|
|
213
213
|
self.stub = storage_service_pb2_grpc.StorageServiceStub(channel)
|
|
214
|
-
logger.debug("Channel client 'storage' initialized
|
|
214
|
+
logger.debug("Channel client 'storage' initialized successfully")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""UserProfile service package."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.services.user_profile.default_user_profile import DefaultUserProfile
|
|
4
|
+
from digitalkin.services.user_profile.grpc_user_profile import GrpcUserProfile
|
|
5
|
+
from digitalkin.services.user_profile.user_profile_strategy import UserProfileServiceError, UserProfileStrategy
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"DefaultUserProfile",
|
|
9
|
+
"GrpcUserProfile",
|
|
10
|
+
"UserProfileServiceError",
|
|
11
|
+
"UserProfileStrategy",
|
|
12
|
+
]
|