digitalkin 0.2.25rc0__py3-none-any.whl → 0.3.2.dev14__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 (122) hide show
  1. base_server/server_async_insecure.py +6 -5
  2. base_server/server_async_secure.py +6 -5
  3. base_server/server_sync_insecure.py +5 -4
  4. base_server/server_sync_secure.py +5 -4
  5. digitalkin/__version__.py +1 -1
  6. digitalkin/core/__init__.py +1 -0
  7. digitalkin/core/common/__init__.py +9 -0
  8. digitalkin/core/common/factories.py +156 -0
  9. digitalkin/core/job_manager/__init__.py +1 -0
  10. digitalkin/{modules → core}/job_manager/base_job_manager.py +138 -32
  11. digitalkin/core/job_manager/single_job_manager.py +373 -0
  12. digitalkin/{modules → core}/job_manager/taskiq_broker.py +121 -26
  13. digitalkin/core/job_manager/taskiq_job_manager.py +541 -0
  14. digitalkin/core/task_manager/__init__.py +1 -0
  15. digitalkin/core/task_manager/base_task_manager.py +539 -0
  16. digitalkin/core/task_manager/local_task_manager.py +108 -0
  17. digitalkin/core/task_manager/remote_task_manager.py +87 -0
  18. digitalkin/core/task_manager/surrealdb_repository.py +266 -0
  19. digitalkin/core/task_manager/task_executor.py +249 -0
  20. digitalkin/core/task_manager/task_session.py +368 -0
  21. digitalkin/grpc_servers/__init__.py +1 -19
  22. digitalkin/grpc_servers/_base_server.py +3 -3
  23. digitalkin/grpc_servers/module_server.py +120 -195
  24. digitalkin/grpc_servers/module_servicer.py +81 -44
  25. digitalkin/grpc_servers/utils/__init__.py +1 -0
  26. digitalkin/grpc_servers/utils/exceptions.py +0 -8
  27. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +25 -9
  28. digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
  29. digitalkin/grpc_servers/utils/utility_schema_extender.py +100 -0
  30. digitalkin/logger.py +64 -27
  31. digitalkin/mixins/__init__.py +19 -0
  32. digitalkin/mixins/base_mixin.py +10 -0
  33. digitalkin/mixins/callback_mixin.py +24 -0
  34. digitalkin/mixins/chat_history_mixin.py +110 -0
  35. digitalkin/mixins/cost_mixin.py +76 -0
  36. digitalkin/mixins/file_history_mixin.py +93 -0
  37. digitalkin/mixins/filesystem_mixin.py +46 -0
  38. digitalkin/mixins/logger_mixin.py +51 -0
  39. digitalkin/mixins/storage_mixin.py +79 -0
  40. digitalkin/models/__init__.py +1 -1
  41. digitalkin/models/core/__init__.py +1 -0
  42. digitalkin/{modules/job_manager → models/core}/job_manager_models.py +3 -11
  43. digitalkin/models/core/task_monitor.py +74 -0
  44. digitalkin/models/grpc_servers/__init__.py +1 -0
  45. digitalkin/{grpc_servers/utils → models/grpc_servers}/models.py +92 -7
  46. digitalkin/models/module/__init__.py +18 -11
  47. digitalkin/models/module/base_types.py +61 -0
  48. digitalkin/models/module/module.py +9 -1
  49. digitalkin/models/module/module_context.py +282 -6
  50. digitalkin/models/module/module_types.py +29 -105
  51. digitalkin/models/module/setup_types.py +490 -0
  52. digitalkin/models/module/tool_cache.py +68 -0
  53. digitalkin/models/module/tool_reference.py +117 -0
  54. digitalkin/models/module/utility.py +167 -0
  55. digitalkin/models/services/__init__.py +9 -0
  56. digitalkin/models/services/cost.py +1 -0
  57. digitalkin/models/services/registry.py +35 -0
  58. digitalkin/models/services/storage.py +39 -5
  59. digitalkin/modules/__init__.py +5 -1
  60. digitalkin/modules/_base_module.py +265 -167
  61. digitalkin/modules/archetype_module.py +6 -1
  62. digitalkin/modules/tool_module.py +16 -3
  63. digitalkin/modules/trigger_handler.py +7 -6
  64. digitalkin/modules/triggers/__init__.py +8 -0
  65. digitalkin/modules/triggers/healthcheck_ping_trigger.py +45 -0
  66. digitalkin/modules/triggers/healthcheck_services_trigger.py +63 -0
  67. digitalkin/modules/triggers/healthcheck_status_trigger.py +52 -0
  68. digitalkin/services/__init__.py +4 -0
  69. digitalkin/services/communication/__init__.py +7 -0
  70. digitalkin/services/communication/communication_strategy.py +76 -0
  71. digitalkin/services/communication/default_communication.py +101 -0
  72. digitalkin/services/communication/grpc_communication.py +234 -0
  73. digitalkin/services/cost/__init__.py +9 -2
  74. digitalkin/services/cost/grpc_cost.py +9 -42
  75. digitalkin/services/filesystem/default_filesystem.py +0 -2
  76. digitalkin/services/filesystem/grpc_filesystem.py +10 -39
  77. digitalkin/services/registry/__init__.py +22 -1
  78. digitalkin/services/registry/default_registry.py +135 -4
  79. digitalkin/services/registry/exceptions.py +47 -0
  80. digitalkin/services/registry/grpc_registry.py +306 -0
  81. digitalkin/services/registry/registry_models.py +15 -0
  82. digitalkin/services/registry/registry_strategy.py +88 -4
  83. digitalkin/services/services_config.py +25 -3
  84. digitalkin/services/services_models.py +5 -1
  85. digitalkin/services/setup/default_setup.py +6 -7
  86. digitalkin/services/setup/grpc_setup.py +52 -15
  87. digitalkin/services/storage/grpc_storage.py +4 -4
  88. digitalkin/services/user_profile/__init__.py +12 -0
  89. digitalkin/services/user_profile/default_user_profile.py +55 -0
  90. digitalkin/services/user_profile/grpc_user_profile.py +69 -0
  91. digitalkin/services/user_profile/user_profile_strategy.py +25 -0
  92. digitalkin/utils/__init__.py +28 -0
  93. digitalkin/utils/arg_parser.py +1 -1
  94. digitalkin/utils/development_mode_action.py +2 -2
  95. digitalkin/utils/dynamic_schema.py +483 -0
  96. digitalkin/utils/package_discover.py +1 -2
  97. digitalkin/utils/schema_splitter.py +207 -0
  98. {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/METADATA +11 -30
  99. digitalkin-0.3.2.dev14.dist-info/RECORD +143 -0
  100. {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/top_level.txt +1 -0
  101. modules/archetype_with_tools_module.py +244 -0
  102. modules/cpu_intensive_module.py +1 -1
  103. modules/dynamic_setup_module.py +338 -0
  104. modules/minimal_llm_module.py +1 -1
  105. modules/text_transform_module.py +1 -1
  106. monitoring/digitalkin_observability/__init__.py +46 -0
  107. monitoring/digitalkin_observability/http_server.py +150 -0
  108. monitoring/digitalkin_observability/interceptors.py +176 -0
  109. monitoring/digitalkin_observability/metrics.py +201 -0
  110. monitoring/digitalkin_observability/prometheus.py +137 -0
  111. monitoring/tests/test_metrics.py +172 -0
  112. services/filesystem_module.py +7 -5
  113. services/storage_module.py +4 -2
  114. digitalkin/grpc_servers/registry_server.py +0 -65
  115. digitalkin/grpc_servers/registry_servicer.py +0 -456
  116. digitalkin/grpc_servers/utils/factory.py +0 -180
  117. digitalkin/modules/job_manager/single_job_manager.py +0 -294
  118. digitalkin/modules/job_manager/taskiq_job_manager.py +0 -290
  119. digitalkin-0.2.25rc0.dist-info/RECORD +0 -89
  120. /digitalkin/{grpc_servers/utils → models/grpc_servers}/types.py +0 -0
  121. {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/WHEEL +0 -0
  122. {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,14 @@
1
1
  """This module is responsible for handling the cost services."""
2
2
 
3
- from digitalkin.services.cost.cost_strategy import CostStrategy
3
+ from digitalkin.services.cost.cost_strategy import CostConfig, CostData, CostStrategy, CostType
4
4
  from digitalkin.services.cost.default_cost import DefaultCost
5
5
  from digitalkin.services.cost.grpc_cost import GrpcCost
6
6
 
7
- __all__ = ["CostStrategy", "DefaultCost", "GrpcCost"]
7
+ __all__ = [
8
+ "CostConfig",
9
+ "CostData",
10
+ "CostStrategy",
11
+ "CostType",
12
+ "DefaultCost",
13
+ "GrpcCost",
14
+ ]
@@ -1,16 +1,14 @@
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 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
12
- from digitalkin.grpc_servers.utils.models import ClientConfig
9
+ from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
13
10
  from digitalkin.logger import logger
11
+ from digitalkin.models.grpc_servers.models import ClientConfig
14
12
  from digitalkin.services.cost.cost_strategy import (
15
13
  CostConfig,
16
14
  CostData,
@@ -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(
@@ -198,8 +198,6 @@ class DefaultFilesystem(FilesystemStrategy):
198
198
  end_idx = start_idx + list_size
199
199
  paginated_files = filtered_files[start_idx:end_idx]
200
200
 
201
- logger.critical(f"{filters=} | {paginated_files=}")
202
-
203
201
  if include_content:
204
202
  for file in paginated_files:
205
203
  file.content = Path(file.storage_uri).read_bytes()
@@ -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 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),
@@ -1,6 +1,27 @@
1
1
  """This module is responsible for handling the registry service."""
2
2
 
3
+ from digitalkin.models.services.registry import (
4
+ ModuleInfo,
5
+ RegistryModuleStatus,
6
+ RegistryModuleType,
7
+ )
3
8
  from digitalkin.services.registry.default_registry import DefaultRegistry
9
+ from digitalkin.services.registry.exceptions import (
10
+ RegistryModuleNotFoundError,
11
+ RegistryServiceError,
12
+ )
13
+ from digitalkin.services.registry.grpc_registry import GrpcRegistry
14
+ from digitalkin.services.registry.registry_models import ModuleStatusInfo
4
15
  from digitalkin.services.registry.registry_strategy import RegistryStrategy
5
16
 
6
- __all__ = ["DefaultRegistry", "RegistryStrategy"]
17
+ __all__ = [
18
+ "DefaultRegistry",
19
+ "GrpcRegistry",
20
+ "ModuleInfo",
21
+ "ModuleStatusInfo",
22
+ "RegistryModuleNotFoundError",
23
+ "RegistryModuleStatus",
24
+ "RegistryModuleType",
25
+ "RegistryServiceError",
26
+ "RegistryStrategy",
27
+ ]
@@ -1,10 +1,141 @@
1
- """Default registry."""
1
+ """Default registry implementation."""
2
2
 
3
+ from typing import ClassVar
4
+
5
+ from digitalkin.models.services.registry import (
6
+ ModuleInfo,
7
+ RegistryModuleStatus,
8
+ RegistryModuleType,
9
+ )
10
+ from digitalkin.services.registry.exceptions import RegistryModuleNotFoundError
11
+ from digitalkin.services.registry.registry_models import ModuleStatusInfo
3
12
  from digitalkin.services.registry.registry_strategy import RegistryStrategy
4
13
 
5
14
 
6
15
  class DefaultRegistry(RegistryStrategy):
7
- """Default registry strategy."""
16
+ """Default registry strategy using in-memory storage."""
17
+
18
+ _modules: ClassVar[dict[str, ModuleInfo]] = {}
19
+
20
+ def discover_by_id(self, module_id: str) -> ModuleInfo:
21
+ """Get module info by ID.
22
+
23
+ Args:
24
+ module_id: The module identifier.
25
+
26
+ Returns:
27
+ ModuleInfo with module details.
28
+
29
+ Raises:
30
+ RegistryModuleNotFoundError: If module not found.
31
+ """
32
+ if module_id not in self._modules:
33
+ raise RegistryModuleNotFoundError(module_id)
34
+ return self._modules[module_id]
35
+
36
+ def search(
37
+ self,
38
+ name: str | None = None,
39
+ module_type: str | None = None,
40
+ organization_id: str | None = None, # noqa: ARG002
41
+ ) -> list[ModuleInfo]:
42
+ """Search for modules by criteria.
43
+
44
+ Args:
45
+ name: Filter by name (partial match).
46
+ module_type: Filter by type (archetype, tool).
47
+ organization_id: Filter by organization (not used in local storage).
48
+
49
+ Returns:
50
+ List of matching modules.
51
+ """
52
+ results = list(self._modules.values())
53
+
54
+ if name:
55
+ results = [m for m in results if name in m.name]
56
+
57
+ if module_type:
58
+ results = [m for m in results if m.module_type == module_type]
59
+
60
+ return results
61
+
62
+ def get_status(self, module_id: str) -> ModuleStatusInfo:
63
+ """Get module status.
64
+
65
+ Args:
66
+ module_id: The module identifier.
67
+
68
+ Returns:
69
+ ModuleStatusInfo with current status.
70
+
71
+ Raises:
72
+ RegistryModuleNotFoundError: If module not found.
73
+ """
74
+ if module_id not in self._modules:
75
+ raise RegistryModuleNotFoundError(module_id)
76
+
77
+ module = self._modules[module_id]
78
+ return ModuleStatusInfo(
79
+ module_id=module_id,
80
+ status=module.status or RegistryModuleStatus.UNSPECIFIED,
81
+ )
82
+
83
+ def register(
84
+ self,
85
+ module_id: str,
86
+ address: str,
87
+ port: int,
88
+ version: str,
89
+ ) -> ModuleInfo | None:
90
+ """Register a module with the registry.
91
+
92
+ Note: Updates existing module or creates new one in local storage.
93
+
94
+ Args:
95
+ module_id: Unique module identifier.
96
+ address: Network address.
97
+ port: Network port.
98
+ version: Module version.
99
+
100
+ Returns:
101
+ ModuleInfo if successful, None otherwise.
102
+ """
103
+ existing = self._modules.get(module_id)
104
+ self._modules[module_id] = ModuleInfo(
105
+ module_id=module_id,
106
+ module_type=existing.module_type if existing else RegistryModuleType.UNSPECIFIED,
107
+ address=address,
108
+ port=port,
109
+ version=version,
110
+ name=existing.name if existing else module_id,
111
+ status=RegistryModuleStatus.ACTIVE,
112
+ )
113
+ return self._modules[module_id]
114
+
115
+ def heartbeat(self, module_id: str) -> RegistryModuleStatus:
116
+ """Send heartbeat to keep module active.
117
+
118
+ Args:
119
+ module_id: The module identifier.
120
+
121
+ Returns:
122
+ Current module status after heartbeat.
123
+
124
+ Raises:
125
+ RegistryModuleNotFoundError: If module not found.
126
+ """
127
+ if module_id not in self._modules:
128
+ raise RegistryModuleNotFoundError(module_id)
8
129
 
9
- def get_by_id(self, module_id: str) -> None:
10
- """Get services from the registry."""
130
+ module = self._modules[module_id]
131
+ # Update status to ACTIVE on heartbeat
132
+ self._modules[module_id] = ModuleInfo(
133
+ module_id=module.module_id,
134
+ module_type=module.module_type,
135
+ address=module.address,
136
+ port=module.port,
137
+ version=module.version,
138
+ name=module.name,
139
+ status=RegistryModuleStatus.ACTIVE,
140
+ )
141
+ return RegistryModuleStatus.ACTIVE
@@ -0,0 +1,47 @@
1
+ """Registry-specific exceptions.
2
+
3
+ This module contains custom exceptions for registry service operations.
4
+ """
5
+
6
+
7
+ class RegistryServiceError(Exception):
8
+ """Base exception for registry service errors."""
9
+
10
+
11
+ class RegistryModuleNotFoundError(RegistryServiceError):
12
+ """Raised when a module is not found in the registry."""
13
+
14
+ def __init__(self, module_id: str) -> None:
15
+ """Initialize the exception.
16
+
17
+ Args:
18
+ module_id: The ID of the module that was not found.
19
+ """
20
+ self.module_id = module_id
21
+ super().__init__(f"Module '{module_id}' not found in registry")
22
+
23
+
24
+ class ModuleAlreadyExistsError(RegistryServiceError):
25
+ """Raised when attempting to register an already-registered module."""
26
+
27
+ def __init__(self, module_id: str) -> None:
28
+ """Initialize the exception.
29
+
30
+ Args:
31
+ module_id: The ID of the module that already exists.
32
+ """
33
+ self.module_id = module_id
34
+ super().__init__(f"Module '{module_id}' already registered")
35
+
36
+
37
+ class InvalidStatusError(RegistryServiceError):
38
+ """Raised when an invalid status is provided."""
39
+
40
+ def __init__(self, status: int) -> None:
41
+ """Initialize the exception.
42
+
43
+ Args:
44
+ status: The invalid status value.
45
+ """
46
+ self.status = status
47
+ super().__init__(f"Invalid module status: {status}")