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.
Files changed (43) hide show
  1. digitalkin/__version__.py +1 -1
  2. digitalkin/core/common/__init__.py +9 -0
  3. digitalkin/core/common/factories.py +156 -0
  4. digitalkin/core/job_manager/base_job_manager.py +128 -28
  5. digitalkin/core/job_manager/single_job_manager.py +80 -25
  6. digitalkin/core/job_manager/taskiq_broker.py +114 -19
  7. digitalkin/core/job_manager/taskiq_job_manager.py +291 -39
  8. digitalkin/core/task_manager/base_task_manager.py +539 -0
  9. digitalkin/core/task_manager/local_task_manager.py +108 -0
  10. digitalkin/core/task_manager/remote_task_manager.py +87 -0
  11. digitalkin/core/task_manager/surrealdb_repository.py +43 -4
  12. digitalkin/core/task_manager/task_executor.py +249 -0
  13. digitalkin/core/task_manager/task_session.py +107 -19
  14. digitalkin/grpc_servers/module_server.py +2 -2
  15. digitalkin/grpc_servers/module_servicer.py +21 -12
  16. digitalkin/grpc_servers/registry_server.py +1 -1
  17. digitalkin/grpc_servers/registry_servicer.py +4 -4
  18. digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
  19. digitalkin/models/core/task_monitor.py +17 -0
  20. digitalkin/models/grpc_servers/models.py +4 -4
  21. digitalkin/models/module/module_context.py +5 -0
  22. digitalkin/models/module/module_types.py +304 -16
  23. digitalkin/modules/_base_module.py +66 -28
  24. digitalkin/services/cost/grpc_cost.py +8 -41
  25. digitalkin/services/filesystem/grpc_filesystem.py +9 -38
  26. digitalkin/services/services_config.py +11 -0
  27. digitalkin/services/services_models.py +3 -1
  28. digitalkin/services/setup/default_setup.py +5 -6
  29. digitalkin/services/setup/grpc_setup.py +51 -14
  30. digitalkin/services/storage/grpc_storage.py +2 -2
  31. digitalkin/services/user_profile/__init__.py +12 -0
  32. digitalkin/services/user_profile/default_user_profile.py +55 -0
  33. digitalkin/services/user_profile/grpc_user_profile.py +69 -0
  34. digitalkin/services/user_profile/user_profile_strategy.py +40 -0
  35. digitalkin/utils/__init__.py +28 -0
  36. digitalkin/utils/dynamic_schema.py +483 -0
  37. {digitalkin-0.3.0rc1.dist-info → digitalkin-0.3.1.dist-info}/METADATA +9 -29
  38. {digitalkin-0.3.0rc1.dist-info → digitalkin-0.3.1.dist-info}/RECORD +42 -30
  39. modules/dynamic_setup_module.py +362 -0
  40. digitalkin/core/task_manager/task_manager.py +0 -439
  41. {digitalkin-0.3.0rc1.dist-info → digitalkin-0.3.1.dist-info}/WHEEL +0 -0
  42. {digitalkin-0.3.0rc1.dist-info → digitalkin-0.3.1.dist-info}/licenses/LICENSE +0 -0
  43. {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
- Raises:
114
- NotImplementedError: If the `secret_format` is not defined.
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
- Raises:
131
- NotImplementedError: If the `input_format` is not defined.
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
- Raises:
148
- NotImplementedError: If the `output_format` is not defined.
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 data.
165
- The setup format is used to initialize an run the module with setup data.
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
- Raises:
168
- NotImplementedError: If the `setup_format` is not defined.
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
- Raises:
186
- NotImplementedError: If the `setup_format` is not defined.
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
- The setup model.
268
+ An instance of the setup model with the provided data.
233
269
  """
234
- return cls.setup_format.get_clean_model(config_fields=config_fields, hidden_fields=True)(**setup_data)
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 callback(self.create_setup_model(wrapper))
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 collections.abc import Generator
4
- from contextlib import contextmanager
5
- from typing import Any, Literal
3
+ from typing import Literal
6
4
 
7
- from digitalkin_proto.digitalkin.cost.v1 import cost_pb2, cost_service_pb2_grpc
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 succesfully")
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._handle_grpc_errors("AddCost"):
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._handle_grpc_errors("GetCost"):
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._handle_grpc_errors("GetCosts"):
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.digitalkin.filesystem.v1 import filesystem_pb2, filesystem_service_pb2_grpc
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 succesfully")
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 GrpcFilesystem._handle_grpc_errors("UploadFiles"):
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 GrpcFilesystem._handle_grpc_errors("GetFile"):
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 GrpcFilesystem._handle_grpc_errors("UpdateFile"):
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 GrpcFilesystem._handle_grpc_errors("DeleteFiles"):
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 GrpcFilesystem._handle_grpc_errors("GetFiles"):
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.grpc_setup import SetupData, SetupVersionData
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 succesfull", setup_id, valid_data)
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 succesfull", setup_version_dict["setup_id"], valid_data)
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.digitalkin.setup.v2 import (
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 succesfully")
33
+ logger.debug("Channel client 'setup' initialized successfully")
34
34
 
35
35
  @contextmanager
36
- def _handle_grpc_errors(self, operation: str) -> Generator[Any, Any, Any]: # noqa: PLR6301
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._handle_grpc_errors("Setup Creation"):
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._handle_grpc_errors("Get Setup"):
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._handle_grpc_errors("Setup Update"):
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._handle_grpc_errors("Setup Deletion"):
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._handle_grpc_errors("Setup Version Creation"):
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._handle_grpc_errors("Get Setup Version"):
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(**json_format.MessageToDict(response.setup_version))
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._handle_grpc_errors("Search Setup Versions"):
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 [SetupVersionData(**json_format.MessageToDict(sv)) for sv in response.setup_versions]
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._handle_grpc_errors("Setup Version Update"):
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._handle_grpc_errors("Setup Version Deletion"):
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.digitalkin.storage.v2 import data_pb2, storage_service_pb2_grpc
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 succesfully")
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
+ ]