digitalkin 0.2.25rc0__py3-none-any.whl → 0.3.1.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 (78) hide show
  1. digitalkin/__version__.py +1 -1
  2. digitalkin/core/__init__.py +1 -0
  3. digitalkin/core/common/__init__.py +9 -0
  4. digitalkin/core/common/factories.py +156 -0
  5. digitalkin/core/job_manager/__init__.py +1 -0
  6. digitalkin/{modules → core}/job_manager/base_job_manager.py +137 -31
  7. digitalkin/core/job_manager/single_job_manager.py +354 -0
  8. digitalkin/{modules → core}/job_manager/taskiq_broker.py +116 -22
  9. digitalkin/core/job_manager/taskiq_job_manager.py +541 -0
  10. digitalkin/core/task_manager/__init__.py +1 -0
  11. digitalkin/core/task_manager/base_task_manager.py +539 -0
  12. digitalkin/core/task_manager/local_task_manager.py +108 -0
  13. digitalkin/core/task_manager/remote_task_manager.py +87 -0
  14. digitalkin/core/task_manager/surrealdb_repository.py +266 -0
  15. digitalkin/core/task_manager/task_executor.py +249 -0
  16. digitalkin/core/task_manager/task_session.py +406 -0
  17. digitalkin/grpc_servers/__init__.py +1 -19
  18. digitalkin/grpc_servers/_base_server.py +3 -3
  19. digitalkin/grpc_servers/module_server.py +27 -43
  20. digitalkin/grpc_servers/module_servicer.py +51 -36
  21. digitalkin/grpc_servers/registry_server.py +2 -2
  22. digitalkin/grpc_servers/registry_servicer.py +4 -4
  23. digitalkin/grpc_servers/utils/__init__.py +1 -0
  24. digitalkin/grpc_servers/utils/exceptions.py +0 -8
  25. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +4 -4
  26. digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
  27. digitalkin/logger.py +64 -27
  28. digitalkin/mixins/__init__.py +19 -0
  29. digitalkin/mixins/base_mixin.py +10 -0
  30. digitalkin/mixins/callback_mixin.py +24 -0
  31. digitalkin/mixins/chat_history_mixin.py +110 -0
  32. digitalkin/mixins/cost_mixin.py +76 -0
  33. digitalkin/mixins/file_history_mixin.py +93 -0
  34. digitalkin/mixins/filesystem_mixin.py +46 -0
  35. digitalkin/mixins/logger_mixin.py +51 -0
  36. digitalkin/mixins/storage_mixin.py +79 -0
  37. digitalkin/models/core/__init__.py +1 -0
  38. digitalkin/{modules/job_manager → models/core}/job_manager_models.py +3 -3
  39. digitalkin/models/core/task_monitor.py +70 -0
  40. digitalkin/models/grpc_servers/__init__.py +1 -0
  41. digitalkin/{grpc_servers/utils → models/grpc_servers}/models.py +5 -5
  42. digitalkin/models/module/__init__.py +2 -0
  43. digitalkin/models/module/module.py +9 -1
  44. digitalkin/models/module/module_context.py +122 -6
  45. digitalkin/models/module/module_types.py +307 -19
  46. digitalkin/models/services/__init__.py +9 -0
  47. digitalkin/models/services/cost.py +1 -0
  48. digitalkin/models/services/storage.py +39 -5
  49. digitalkin/modules/_base_module.py +123 -118
  50. digitalkin/modules/tool_module.py +10 -2
  51. digitalkin/modules/trigger_handler.py +7 -6
  52. digitalkin/services/cost/__init__.py +9 -2
  53. digitalkin/services/cost/grpc_cost.py +9 -42
  54. digitalkin/services/filesystem/default_filesystem.py +0 -2
  55. digitalkin/services/filesystem/grpc_filesystem.py +10 -39
  56. digitalkin/services/setup/default_setup.py +5 -6
  57. digitalkin/services/setup/grpc_setup.py +52 -15
  58. digitalkin/services/storage/grpc_storage.py +4 -4
  59. digitalkin/services/user_profile/__init__.py +1 -0
  60. digitalkin/services/user_profile/default_user_profile.py +55 -0
  61. digitalkin/services/user_profile/grpc_user_profile.py +69 -0
  62. digitalkin/services/user_profile/user_profile_strategy.py +40 -0
  63. digitalkin/utils/__init__.py +28 -0
  64. digitalkin/utils/arg_parser.py +1 -1
  65. digitalkin/utils/development_mode_action.py +2 -2
  66. digitalkin/utils/dynamic_schema.py +483 -0
  67. digitalkin/utils/package_discover.py +1 -2
  68. {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.1.dev2.dist-info}/METADATA +11 -30
  69. digitalkin-0.3.1.dev2.dist-info/RECORD +119 -0
  70. modules/dynamic_setup_module.py +362 -0
  71. digitalkin/grpc_servers/utils/factory.py +0 -180
  72. digitalkin/modules/job_manager/single_job_manager.py +0 -294
  73. digitalkin/modules/job_manager/taskiq_job_manager.py +0 -290
  74. digitalkin-0.2.25rc0.dist-info/RECORD +0 -89
  75. /digitalkin/{grpc_servers/utils → models/grpc_servers}/types.py +0 -0
  76. {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.1.dev2.dist-info}/WHEEL +0 -0
  77. {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.1.dev2.dist-info}/licenses/LICENSE +0 -0
  78. {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.1.dev2.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,15 @@
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
13
- from digitalkin.grpc_servers.utils.models import ClientConfig
10
+ from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
14
11
  from digitalkin.logger import logger
12
+ from digitalkin.models.grpc_servers.models import ClientConfig
15
13
  from digitalkin.services.filesystem.filesystem_strategy import (
16
14
  FileFilter,
17
15
  FilesystemRecord,
@@ -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),
@@ -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
  )
@@ -15,8 +15,8 @@ from pydantic import ValidationError
15
15
 
16
16
  from digitalkin.grpc_servers.utils.exceptions import ServerError
17
17
  from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
18
- from digitalkin.grpc_servers.utils.models import ClientConfig
19
18
  from digitalkin.logger import logger
19
+ from digitalkin.models.grpc_servers.models import ClientConfig
20
20
  from digitalkin.services.setup.setup_strategy import SetupData, SetupServiceError, SetupStrategy, SetupVersionData
21
21
 
22
22
 
@@ -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,13 +1,13 @@
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
7
7
 
8
8
  from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
9
- from digitalkin.grpc_servers.utils.models import ClientConfig
10
9
  from digitalkin.logger import logger
10
+ from digitalkin.models.grpc_servers.models import ClientConfig
11
11
  from digitalkin.services.storage.storage_strategy import (
12
12
  DataType,
13
13
  StorageRecord,
@@ -128,7 +128,7 @@ class GrpcStorage(StorageStrategy, GrpcClientWrapper):
128
128
  resp = self.exec_grpc_query("UpdateRecord", req)
129
129
  return self._build_record_from_proto(resp.stored_data)
130
130
  except Exception:
131
- logger.exception("gRPC UpdateRecord failed for %s:%s", collection, record_id)
131
+ logger.warning("gRPC UpdateRecord failed for %s:%s", collection, record_id)
132
132
  return None
133
133
 
134
134
  def _remove(self, collection: str, record_id: str) -> bool:
@@ -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 @@
1
+ """UserProfile service package."""
@@ -0,0 +1,55 @@
1
+ """Default user profile implementation."""
2
+
3
+ from typing import Any
4
+
5
+ from digitalkin.logger import logger
6
+ from digitalkin.services.user_profile.user_profile_strategy import (
7
+ UserProfileServiceError,
8
+ UserProfileStrategy,
9
+ )
10
+
11
+
12
+ class DefaultUserProfile(UserProfileStrategy):
13
+ """Default user profile strategy with in-memory storage."""
14
+
15
+ def __init__(
16
+ self,
17
+ mission_id: str,
18
+ setup_id: str,
19
+ setup_version_id: str,
20
+ ) -> None:
21
+ """Initialize the strategy.
22
+
23
+ Args:
24
+ mission_id: The ID of the mission this strategy is associated with
25
+ setup_id: The ID of the setup
26
+ setup_version_id: The ID of the setup version
27
+ """
28
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id)
29
+ self.db: dict[str, dict[str, Any]] = {}
30
+
31
+ def get_user_profile(self) -> dict[str, Any]:
32
+ """Get user profile from in-memory storage.
33
+
34
+ Returns:
35
+ dict[str, Any]: User profile data
36
+
37
+ Raises:
38
+ UserProfileServiceError: If the user profile is not found
39
+ """
40
+ if self.mission_id not in self.db:
41
+ msg = f"User profile for mission {self.mission_id} not found in the database."
42
+ logger.warning(msg)
43
+ raise UserProfileServiceError(msg)
44
+
45
+ logger.debug(f"Retrieved user profile for mission_id: {self.mission_id}")
46
+ return self.db[self.mission_id]
47
+
48
+ def add_user_profile(self, user_profile_data: dict[str, Any]) -> None:
49
+ """Add a user profile to the in-memory database (helper for testing).
50
+
51
+ Args:
52
+ user_profile_data: Dictionary containing user profile data
53
+ """
54
+ self.db[self.mission_id] = user_profile_data
55
+ logger.debug(f"Added user profile for mission_id: {self.mission_id}")
@@ -0,0 +1,69 @@
1
+ """Digital Kin UserProfile Service gRPC Client."""
2
+
3
+ from typing import Any
4
+
5
+ from digitalkin_proto.agentic_mesh_protocol.user_profile.v1 import (
6
+ user_profile_pb2,
7
+ user_profile_service_pb2_grpc,
8
+ )
9
+ from google.protobuf import json_format
10
+
11
+ from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
12
+ from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
13
+ from digitalkin.logger import logger
14
+ from digitalkin.models.grpc_servers.models import ClientConfig
15
+ from digitalkin.services.user_profile.user_profile_strategy import UserProfileServiceError, UserProfileStrategy
16
+
17
+
18
+ class GrpcUserProfile(UserProfileStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
19
+ """This class implements the gRPC user profile service."""
20
+
21
+ def __init__(
22
+ self,
23
+ mission_id: str,
24
+ setup_id: str,
25
+ setup_version_id: str,
26
+ client_config: ClientConfig,
27
+ ) -> None:
28
+ """Initialize the user profile service.
29
+
30
+ Args:
31
+ mission_id: The ID of the mission this strategy is associated with
32
+ setup_id: The ID of the setup
33
+ setup_version_id: The ID of the setup version
34
+ client_config: Client configuration for gRPC connection
35
+ """
36
+ super().__init__(mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id)
37
+ channel = self._init_channel(client_config)
38
+ self.stub = user_profile_service_pb2_grpc.UserProfileServiceStub(channel)
39
+ logger.debug("Channel client 'UserProfile' initialized successfully")
40
+
41
+ def get_user_profile(self) -> dict[str, Any]:
42
+ """Get user profile by mission_id (which maps to user_id).
43
+
44
+ Returns:
45
+ dict[str, Any]: User profile data
46
+
47
+ Raises:
48
+ UserProfileServiceError: If the user profile cannot be retrieved
49
+ ServerError: If gRPC operation fails
50
+ """
51
+ with self.handle_grpc_errors("GetUserProfile", UserProfileServiceError):
52
+ # mission_id maps to the user context in the proto request
53
+ request = user_profile_pb2.GetUserProfileRequest(mission_id=self.mission_id)
54
+ response = self.exec_grpc_query("GetUserProfile", request)
55
+
56
+ if not response.success:
57
+ msg = f"Failed to get user profile for mission_id: {self.mission_id}"
58
+ logger.error(msg)
59
+ raise UserProfileServiceError(msg)
60
+
61
+ # Convert proto to dict
62
+ user_profile_dict = json_format.MessageToDict(
63
+ response.user_profile,
64
+ preserving_proto_field_name=True,
65
+ always_print_fields_with_no_presence=True,
66
+ )
67
+
68
+ logger.debug(f"Retrieved user profile for mission_id: {self.mission_id}")
69
+ return user_profile_dict
@@ -0,0 +1,40 @@
1
+ """This module contains the abstract base class for UserProfile strategies."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+ from digitalkin.services.base_strategy import BaseStrategy
7
+
8
+
9
+ class UserProfileServiceError(Exception):
10
+ """Base exception for UserProfile service errors."""
11
+
12
+
13
+ class UserProfileStrategy(BaseStrategy, ABC):
14
+ """Abstract base class for UserProfile strategies."""
15
+
16
+ def __init__(
17
+ self,
18
+ mission_id: str,
19
+ setup_id: str,
20
+ setup_version_id: str,
21
+ ) -> None:
22
+ """Initialize the strategy.
23
+
24
+ Args:
25
+ mission_id: The ID of the mission this strategy is associated with
26
+ setup_id: The ID of the setup
27
+ setup_version_id: The ID of the setup version this strategy is associated with
28
+ """
29
+ super().__init__(mission_id, setup_id, setup_version_id)
30
+
31
+ @abstractmethod
32
+ def get_user_profile(self) -> dict[str, Any]:
33
+ """Get user profile data.
34
+
35
+ Returns:
36
+ dict[str, Any]: User profile data
37
+
38
+ Raises:
39
+ UserProfileServiceError: If the user profile cannot be retrieved
40
+ """
@@ -1 +1,29 @@
1
1
  """General utils folder."""
2
+
3
+ from digitalkin.utils.dynamic_schema import (
4
+ DEFAULT_TIMEOUT,
5
+ DynamicField,
6
+ Fetcher,
7
+ ResolveResult,
8
+ get_dynamic_metadata,
9
+ get_fetchers,
10
+ has_dynamic,
11
+ resolve,
12
+ resolve_safe,
13
+ )
14
+
15
+ # Alias for cleaner API: `Dynamic` is shorter than `DynamicField`
16
+ Dynamic = DynamicField
17
+
18
+ __all__ = [
19
+ "DEFAULT_TIMEOUT",
20
+ "Dynamic",
21
+ "DynamicField",
22
+ "Fetcher",
23
+ "ResolveResult",
24
+ "get_dynamic_metadata",
25
+ "get_fetchers",
26
+ "has_dynamic",
27
+ "resolve",
28
+ "resolve_safe",
29
+ ]
@@ -55,7 +55,7 @@ class ArgParser:
55
55
  """
56
56
 
57
57
  class HelpAction(_HelpAction):
58
- """."""
58
+ """Custom HelpAction to display subparsers helps too."""
59
59
 
60
60
  def __call__(
61
61
  self,
@@ -13,7 +13,7 @@ logger.setLevel(logging.INFO)
13
13
 
14
14
 
15
15
  class DevelopmentModeMappingAction(Action):
16
- """."""
16
+ """ArgParse Action to map an environment variable to a ServicesMode enum."""
17
17
 
18
18
  def __init__(
19
19
  self,
@@ -22,7 +22,7 @@ class DevelopmentModeMappingAction(Action):
22
22
  default: str | None = None,
23
23
  **kwargs: dict[str, Any],
24
24
  ) -> None:
25
- """."""
25
+ """Initialize the DevelopmentModeMappingAction."""
26
26
  default = ServicesMode(os.environ.get(env_var, default))
27
27
 
28
28
  if required and default: