digitalkin 0.3.2.dev2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. base_server/__init__.py +1 -0
  2. base_server/mock/__init__.py +5 -0
  3. base_server/mock/mock_pb2.py +39 -0
  4. base_server/mock/mock_pb2_grpc.py +102 -0
  5. base_server/server_async_insecure.py +125 -0
  6. base_server/server_async_secure.py +143 -0
  7. base_server/server_sync_insecure.py +103 -0
  8. base_server/server_sync_secure.py +122 -0
  9. digitalkin/__init__.py +8 -0
  10. digitalkin/__version__.py +8 -0
  11. digitalkin/core/__init__.py +1 -0
  12. digitalkin/core/common/__init__.py +9 -0
  13. digitalkin/core/common/factories.py +156 -0
  14. digitalkin/core/job_manager/__init__.py +1 -0
  15. digitalkin/core/job_manager/base_job_manager.py +288 -0
  16. digitalkin/core/job_manager/single_job_manager.py +354 -0
  17. digitalkin/core/job_manager/taskiq_broker.py +311 -0
  18. digitalkin/core/job_manager/taskiq_job_manager.py +541 -0
  19. digitalkin/core/task_manager/__init__.py +1 -0
  20. digitalkin/core/task_manager/base_task_manager.py +539 -0
  21. digitalkin/core/task_manager/local_task_manager.py +108 -0
  22. digitalkin/core/task_manager/remote_task_manager.py +87 -0
  23. digitalkin/core/task_manager/surrealdb_repository.py +266 -0
  24. digitalkin/core/task_manager/task_executor.py +249 -0
  25. digitalkin/core/task_manager/task_session.py +406 -0
  26. digitalkin/grpc_servers/__init__.py +1 -0
  27. digitalkin/grpc_servers/_base_server.py +486 -0
  28. digitalkin/grpc_servers/module_server.py +208 -0
  29. digitalkin/grpc_servers/module_servicer.py +516 -0
  30. digitalkin/grpc_servers/utils/__init__.py +1 -0
  31. digitalkin/grpc_servers/utils/exceptions.py +29 -0
  32. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +88 -0
  33. digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
  34. digitalkin/grpc_servers/utils/utility_schema_extender.py +97 -0
  35. digitalkin/logger.py +157 -0
  36. digitalkin/mixins/__init__.py +19 -0
  37. digitalkin/mixins/base_mixin.py +10 -0
  38. digitalkin/mixins/callback_mixin.py +24 -0
  39. digitalkin/mixins/chat_history_mixin.py +110 -0
  40. digitalkin/mixins/cost_mixin.py +76 -0
  41. digitalkin/mixins/file_history_mixin.py +93 -0
  42. digitalkin/mixins/filesystem_mixin.py +46 -0
  43. digitalkin/mixins/logger_mixin.py +51 -0
  44. digitalkin/mixins/storage_mixin.py +79 -0
  45. digitalkin/models/__init__.py +8 -0
  46. digitalkin/models/core/__init__.py +1 -0
  47. digitalkin/models/core/job_manager_models.py +36 -0
  48. digitalkin/models/core/task_monitor.py +70 -0
  49. digitalkin/models/grpc_servers/__init__.py +1 -0
  50. digitalkin/models/grpc_servers/models.py +275 -0
  51. digitalkin/models/grpc_servers/types.py +24 -0
  52. digitalkin/models/module/__init__.py +25 -0
  53. digitalkin/models/module/module.py +40 -0
  54. digitalkin/models/module/module_context.py +149 -0
  55. digitalkin/models/module/module_types.py +393 -0
  56. digitalkin/models/module/utility.py +146 -0
  57. digitalkin/models/services/__init__.py +10 -0
  58. digitalkin/models/services/cost.py +54 -0
  59. digitalkin/models/services/registry.py +42 -0
  60. digitalkin/models/services/storage.py +44 -0
  61. digitalkin/modules/__init__.py +11 -0
  62. digitalkin/modules/_base_module.py +517 -0
  63. digitalkin/modules/archetype_module.py +23 -0
  64. digitalkin/modules/tool_module.py +23 -0
  65. digitalkin/modules/trigger_handler.py +48 -0
  66. digitalkin/modules/triggers/__init__.py +12 -0
  67. digitalkin/modules/triggers/healthcheck_ping_trigger.py +45 -0
  68. digitalkin/modules/triggers/healthcheck_services_trigger.py +63 -0
  69. digitalkin/modules/triggers/healthcheck_status_trigger.py +52 -0
  70. digitalkin/py.typed +0 -0
  71. digitalkin/services/__init__.py +30 -0
  72. digitalkin/services/agent/__init__.py +6 -0
  73. digitalkin/services/agent/agent_strategy.py +19 -0
  74. digitalkin/services/agent/default_agent.py +13 -0
  75. digitalkin/services/base_strategy.py +22 -0
  76. digitalkin/services/communication/__init__.py +7 -0
  77. digitalkin/services/communication/communication_strategy.py +76 -0
  78. digitalkin/services/communication/default_communication.py +101 -0
  79. digitalkin/services/communication/grpc_communication.py +223 -0
  80. digitalkin/services/cost/__init__.py +14 -0
  81. digitalkin/services/cost/cost_strategy.py +100 -0
  82. digitalkin/services/cost/default_cost.py +114 -0
  83. digitalkin/services/cost/grpc_cost.py +138 -0
  84. digitalkin/services/filesystem/__init__.py +7 -0
  85. digitalkin/services/filesystem/default_filesystem.py +417 -0
  86. digitalkin/services/filesystem/filesystem_strategy.py +252 -0
  87. digitalkin/services/filesystem/grpc_filesystem.py +317 -0
  88. digitalkin/services/identity/__init__.py +6 -0
  89. digitalkin/services/identity/default_identity.py +15 -0
  90. digitalkin/services/identity/identity_strategy.py +14 -0
  91. digitalkin/services/registry/__init__.py +27 -0
  92. digitalkin/services/registry/default_registry.py +141 -0
  93. digitalkin/services/registry/exceptions.py +47 -0
  94. digitalkin/services/registry/grpc_registry.py +306 -0
  95. digitalkin/services/registry/registry_models.py +43 -0
  96. digitalkin/services/registry/registry_strategy.py +98 -0
  97. digitalkin/services/services_config.py +200 -0
  98. digitalkin/services/services_models.py +65 -0
  99. digitalkin/services/setup/__init__.py +1 -0
  100. digitalkin/services/setup/default_setup.py +219 -0
  101. digitalkin/services/setup/grpc_setup.py +343 -0
  102. digitalkin/services/setup/setup_strategy.py +145 -0
  103. digitalkin/services/snapshot/__init__.py +6 -0
  104. digitalkin/services/snapshot/default_snapshot.py +39 -0
  105. digitalkin/services/snapshot/snapshot_strategy.py +30 -0
  106. digitalkin/services/storage/__init__.py +7 -0
  107. digitalkin/services/storage/default_storage.py +228 -0
  108. digitalkin/services/storage/grpc_storage.py +214 -0
  109. digitalkin/services/storage/storage_strategy.py +273 -0
  110. digitalkin/services/user_profile/__init__.py +12 -0
  111. digitalkin/services/user_profile/default_user_profile.py +55 -0
  112. digitalkin/services/user_profile/grpc_user_profile.py +69 -0
  113. digitalkin/services/user_profile/user_profile_strategy.py +40 -0
  114. digitalkin/utils/__init__.py +29 -0
  115. digitalkin/utils/arg_parser.py +92 -0
  116. digitalkin/utils/development_mode_action.py +51 -0
  117. digitalkin/utils/dynamic_schema.py +483 -0
  118. digitalkin/utils/llm_ready_schema.py +75 -0
  119. digitalkin/utils/package_discover.py +357 -0
  120. digitalkin-0.3.2.dev2.dist-info/METADATA +602 -0
  121. digitalkin-0.3.2.dev2.dist-info/RECORD +131 -0
  122. digitalkin-0.3.2.dev2.dist-info/WHEEL +5 -0
  123. digitalkin-0.3.2.dev2.dist-info/licenses/LICENSE +430 -0
  124. digitalkin-0.3.2.dev2.dist-info/top_level.txt +4 -0
  125. modules/__init__.py +0 -0
  126. modules/cpu_intensive_module.py +280 -0
  127. modules/dynamic_setup_module.py +338 -0
  128. modules/minimal_llm_module.py +347 -0
  129. modules/text_transform_module.py +203 -0
  130. services/filesystem_module.py +200 -0
  131. services/storage_module.py +206 -0
@@ -0,0 +1,65 @@
1
+ """This module contains the strategy models for the services."""
2
+
3
+ from enum import Enum
4
+ from typing import Generic, TypeVar
5
+
6
+ from pydantic import BaseModel
7
+
8
+ from digitalkin.logger import logger
9
+ from digitalkin.services.agent import AgentStrategy
10
+ from digitalkin.services.communication import CommunicationStrategy
11
+ from digitalkin.services.cost import CostStrategy
12
+ from digitalkin.services.filesystem import FilesystemStrategy
13
+ from digitalkin.services.identity import IdentityStrategy
14
+ from digitalkin.services.registry import RegistryStrategy
15
+ from digitalkin.services.snapshot import SnapshotStrategy
16
+ from digitalkin.services.storage import StorageStrategy
17
+ from digitalkin.services.user_profile import UserProfileStrategy
18
+
19
+ # Define type variables
20
+ T = TypeVar(
21
+ "T",
22
+ bound=AgentStrategy
23
+ | CommunicationStrategy
24
+ | CostStrategy
25
+ | FilesystemStrategy
26
+ | IdentityStrategy
27
+ | RegistryStrategy
28
+ | SnapshotStrategy
29
+ | StorageStrategy
30
+ | UserProfileStrategy,
31
+ )
32
+
33
+
34
+ class ServicesMode(str, Enum):
35
+ """Mode for strategy execution."""
36
+
37
+ LOCAL = "local"
38
+ REMOTE = "remote"
39
+
40
+
41
+ class ServicesStrategy(BaseModel, Generic[T]):
42
+ """Service class describing the available services in a Module with local and remote attributes.
43
+
44
+ Attributes:
45
+ local: type
46
+ remote: type
47
+ """
48
+
49
+ local: type[T]
50
+ remote: type[T]
51
+
52
+ def __getitem__(self, mode: str) -> type[T]:
53
+ """Get the service strategy based on the mode.
54
+
55
+ Args:
56
+ mode (str): The mode to get the strategy for.
57
+
58
+ Returns:
59
+ The strategy based on the mode.
60
+ """
61
+ try:
62
+ return getattr(self, mode)
63
+ except AttributeError:
64
+ logger.exception("Unknown mode: %s, available modes are: %s", mode, ServicesMode.__members__)
65
+ return getattr(self, ServicesMode.LOCAL.value)
@@ -0,0 +1 @@
1
+ """This module is responsible for handling the setup service."""
@@ -0,0 +1,219 @@
1
+ """This module contains the abstract base class for setup strategies."""
2
+
3
+ import secrets
4
+ import string
5
+ from typing import Any
6
+
7
+ from pydantic import ValidationError
8
+
9
+ from digitalkin.logger import logger
10
+ from digitalkin.services.setup.setup_strategy import SetupData, SetupServiceError, SetupStrategy, SetupVersionData
11
+
12
+
13
+ class DefaultSetup(SetupStrategy):
14
+ """Abstract base class for setup strategies."""
15
+
16
+ setups: dict[str, SetupData]
17
+ setup_versions: dict[str, dict[str, SetupVersionData]]
18
+
19
+ def __init__(self) -> None:
20
+ """Initialize the default setup strategy."""
21
+ super().__init__()
22
+ self.setups = {}
23
+ self.setup_versions = {}
24
+
25
+ def create_setup(self, setup_dict: dict[str, Any]) -> str:
26
+ """Create a new setup with comprehensive validation.
27
+
28
+ Args:
29
+ setup_dict: Dictionary containing setup details.
30
+
31
+ Returns:
32
+ bool: Success status of setup creation.
33
+
34
+ Raises:
35
+ ValidationError: If setup data is invalid.
36
+ GrpcOperationError: If gRPC operation fails.
37
+ """
38
+ try:
39
+ valid_data = SetupData.model_validate(setup_dict["data"]) # Revalidates instance
40
+ except ValidationError:
41
+ logger.exception("Validation failed for model SetupData")
42
+ return ""
43
+
44
+ setup_id = setup_dict.get(
45
+ "setup_id", "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
46
+ )
47
+ valid_data.id = setup_id
48
+ self.setups[setup_id] = valid_data
49
+ logger.debug("CREATE SETUP DATA %s:%s successful", setup_id, valid_data)
50
+ return setup_id
51
+
52
+ def get_setup(self, setup_dict: dict[str, Any]) -> SetupData:
53
+ """Retrieve a setup by its unique identifier.
54
+
55
+ Args:
56
+ setup_dict: Dictionary with 'name' and optional 'version'.
57
+
58
+ Raises:
59
+ SetupServiceError: setup_id does not exist.
60
+
61
+ Returns:
62
+ Dict[str, Any]: Setup details including optional setup version.
63
+ """
64
+ logger.debug("GET setup_id = %s", setup_dict["setup_id"])
65
+ if setup_dict["setup_id"] not in self.setups:
66
+ msg = f"GET setup_id = {setup_dict['setup_id']}: setup_id DOESN'T EXIST"
67
+ logger.error(msg)
68
+ raise SetupServiceError(msg)
69
+ return self.setups[setup_dict["setup_id"]]
70
+
71
+ def update_setup(self, setup_dict: dict[str, Any]) -> bool:
72
+ """Update an existing setup.
73
+
74
+ Args:
75
+ setup_dict: Dictionary with setup update details.
76
+
77
+ Raises:
78
+ ValidationError: setup object failed validation.
79
+
80
+ Returns:
81
+ bool: Success status of the update operation.
82
+ """
83
+ if setup_dict["setup_id"] not in self.setups:
84
+ logger.debug("UPDATE setup_id = %s: setup_id DOESN'T EXIST", setup_dict["setup_id"])
85
+ return False
86
+
87
+ try:
88
+ valid_data = SetupData.model_validate(setup_dict["data"]) # Revalidates instance
89
+ except ValidationError:
90
+ logger.exception("Validation failed for model SetupData")
91
+ return False
92
+
93
+ self.setups[setup_dict["update_id"]] = valid_data
94
+ return True
95
+
96
+ def delete_setup(self, setup_dict: dict[str, Any]) -> bool:
97
+ """Delete a setup by its unique identifier.
98
+
99
+ Args:
100
+ setup_dict: Dictionary with the setup 'name'.
101
+
102
+ Returns:
103
+ bool: Success status of deletion.
104
+ """
105
+ if setup_dict["setup_id"] not in self.setups:
106
+ logger.debug("UPDATE setup_id = %s: setup_id DOESN'T EXIST", setup_dict["setup_id"])
107
+ return False
108
+ del self.setups[setup_dict["setup_id"]]
109
+ return True
110
+
111
+ def create_setup_version(self, setup_version_dict: dict[str, Any]) -> str:
112
+ """Create a new setup version.
113
+
114
+ Args:
115
+ setup_version_dict: Dictionary with setup version details.
116
+
117
+ Raises:
118
+ SetupServiceError: setup object failed validation.
119
+
120
+ Returns:
121
+ str: version of setup version creation.
122
+ """
123
+ try:
124
+ valid_data = SetupVersionData.model_validate(setup_version_dict["data"]) # Revalidates instance
125
+ except ValidationError:
126
+ msg = "Validation failed for model SetupVersionData"
127
+ logger.exception(msg)
128
+ raise SetupServiceError(msg)
129
+
130
+ if setup_version_dict["setup_id"] not in self.setup_versions:
131
+ self.setup_versions[setup_version_dict["setup_id"]] = {}
132
+ self.setup_versions[setup_version_dict["setup_id"]][valid_data.version] = valid_data
133
+ logger.debug("CREATE SETUP VERSION DATA %s:%s successful", setup_version_dict["setup_id"], valid_data)
134
+ return valid_data.version
135
+
136
+ def get_setup_version(self, setup_version_dict: dict[str, Any]) -> SetupVersionData:
137
+ """Retrieve a setup version by its unique identifier.
138
+
139
+ Args:
140
+ setup_version_dict: Dictionary with the setup version 'name'.
141
+
142
+ Raises:
143
+ SetupServiceError: setup_id does not exist.
144
+
145
+ Returns:
146
+ Dict[str, Any]: Setup version details.
147
+ """
148
+ logger.debug("GET setup_id = %s: version = %s", setup_version_dict["setup_id"], setup_version_dict["version"])
149
+ if setup_version_dict["setup_id"] not in self.setup_versions:
150
+ msg = f"GET setup_id = {setup_version_dict['setup_id']}: setup_id DOESN'T EXIST"
151
+ logger.error(msg)
152
+ raise SetupServiceError(msg)
153
+
154
+ return self.setup_versions[setup_version_dict["setup_id"]][setup_version_dict["version"]]
155
+
156
+ def search_setup_versions(self, setup_version_dict: dict[str, Any]) -> list[SetupVersionData]:
157
+ """Search for setup versions based on filters.
158
+
159
+ Args:
160
+ setup_version_dict: Dictionary with optional 'name' or 'query_versions' filters.
161
+
162
+ Raises:
163
+ SetupServiceError: setup_id does not exist.
164
+
165
+ Returns:
166
+ List[SetupVersionData]: A list of matching setup version details.
167
+ """
168
+ if setup_version_dict["setup_id"] not in self.setup_versions:
169
+ msg = f"GET setup_id = {setup_version_dict['setup_id']}: setup_id DOESN'T EXIST"
170
+ logger.error(msg)
171
+ raise SetupServiceError(msg)
172
+
173
+ return [
174
+ value
175
+ for value in self.setup_versions[setup_version_dict["setup_id"]].values()
176
+ if setup_version_dict["query_versions"] in value.version
177
+ ]
178
+
179
+ def update_setup_version(self, setup_version_dict: dict[str, Any]) -> bool:
180
+ """Update an existing setup version.
181
+
182
+ Args:
183
+ setup_version_dict: Dictionary with setup version update details.
184
+
185
+ Returns:
186
+ bool: Success status of the update operation.
187
+ """
188
+ if setup_version_dict["setup_id"] not in self.setup_versions:
189
+ logger.debug("UPDATE setup_id = %s: setup_id DOESN'T EXIST", setup_version_dict["setup_id"])
190
+ return False
191
+
192
+ if setup_version_dict["version"] not in self.setup_versions[setup_version_dict["setup_id"]]:
193
+ logger.debug("UPDATE setup_id = %s: setup_id DOESN'T EXIST", setup_version_dict["setup_id"])
194
+ return False
195
+
196
+ try:
197
+ valid_data = SetupVersionData.model_validate(setup_version_dict["data"])
198
+ except ValidationError:
199
+ logger.exception("Validation failed for model SetupVersionData")
200
+ return False
201
+
202
+ self.setup_versions[setup_version_dict["setup_id"]][setup_version_dict["version"]] = valid_data
203
+ return True
204
+
205
+ def delete_setup_version(self, setup_version_dict: dict[str, Any]) -> bool:
206
+ """Delete a setup version by its unique identifier.
207
+
208
+ Args:
209
+ setup_version_dict: Dictionary with the setup version 'name'.
210
+
211
+ Returns:
212
+ bool: Success status of version deletion.
213
+ """
214
+ if setup_version_dict["setup_id"] not in self.setup_versions:
215
+ logger.debug("UPDATE setup_id = %s: setup_id DOESN'T EXIST", setup_version_dict["setup_id"])
216
+ return False
217
+
218
+ del self.setup_versions[setup_version_dict["setup_id"]][setup_version_dict["version"]]
219
+ return True
@@ -0,0 +1,343 @@
1
+ """Digital Kin Setup Service gRPC Client."""
2
+
3
+ from collections.abc import Generator
4
+ from contextlib import contextmanager
5
+ from typing import Any
6
+
7
+ import grpc
8
+ from agentic_mesh_protocol.setup.v1 import (
9
+ setup_pb2,
10
+ setup_service_pb2_grpc,
11
+ )
12
+ from google.protobuf import json_format
13
+ from google.protobuf.struct_pb2 import Struct
14
+ from pydantic import ValidationError
15
+
16
+ from digitalkin.grpc_servers.utils.exceptions import ServerError
17
+ from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
18
+ from digitalkin.logger import logger
19
+ from digitalkin.models.grpc_servers.models import ClientConfig
20
+ from digitalkin.services.setup.setup_strategy import SetupData, SetupServiceError, SetupStrategy, SetupVersionData
21
+
22
+
23
+ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
24
+ """This class implements the gRPC setup service."""
25
+
26
+ def __post_init__(self, config: ClientConfig) -> None:
27
+ """Init the channel from a config file.
28
+
29
+ Need to be call if the user register a gRPC channel.
30
+ """
31
+ channel = self._init_channel(config)
32
+ self.stub = setup_service_pb2_grpc.SetupServiceStub(channel)
33
+ logger.debug("Channel client 'setup' initialized successfully")
34
+
35
+ @contextmanager
36
+ def handle_grpc_errors(self, operation: str) -> Generator[Any, Any, Any]: # noqa: PLR6301
37
+ """Context manager for consistent gRPC error handling.
38
+
39
+ Yields:
40
+ Allow error handling in context.
41
+
42
+ Args:
43
+ operation: Description of the operation being performed.
44
+
45
+ Raises:
46
+ ValueError: Error wiht the model validation.
47
+ ServerError: from gRPC Client.
48
+ SetupServiceError: setup service internal.
49
+ """
50
+ try:
51
+ yield
52
+ except ValidationError as e:
53
+ msg = f"Invalid data for {operation}"
54
+ logger.exception(msg)
55
+ raise ValueError(msg) from e
56
+ except grpc.RpcError as e:
57
+ msg = f"gRPC {operation} failed: {e}"
58
+ logger.exception(msg)
59
+ raise ServerError(msg) from e
60
+ except Exception as e:
61
+ msg = f"Unexpected error in {operation}"
62
+ logger.exception(msg)
63
+ raise SetupServiceError(msg) from e
64
+
65
+ def create_setup(self, setup_dict: dict[str, Any]) -> str:
66
+ """Create a new setup with comprehensive validation.
67
+
68
+ Args:
69
+ setup_dict: Dictionary containing setup details.
70
+
71
+ Returns:
72
+ bool: Success status of setup creation.
73
+
74
+ Raises:
75
+ ValidationError: If setup data is invalid.
76
+ ServerError: If gRPC operation fails.
77
+ SetupServiceError: For any unexpected internal error.
78
+ """
79
+ with self.handle_grpc_errors("Setup Creation"):
80
+ valid_data = SetupData.model_validate(setup_dict)
81
+
82
+ request = setup_pb2.CreateSetupRequest(
83
+ name=valid_data.name,
84
+ organisation_id=valid_data.organisation_id,
85
+ owner_id=valid_data.owner_id,
86
+ module_id=valid_data.module_id,
87
+ current_setup_version=setup_pb2.SetupVersion(**valid_data.current_setup_version.model_dump()),
88
+ )
89
+ response = self.exec_grpc_query("CreateSetup", request)
90
+ logger.debug("Setup '%s' query sent successfully", valid_data.name)
91
+ return response
92
+
93
+ def get_setup(self, setup_dict: dict[str, Any]) -> SetupData:
94
+ """Retrieve a setup by its unique identifier.
95
+
96
+ Args:
97
+ setup_dict: Dictionary with 'name' and optional 'version'.
98
+
99
+ Returns:
100
+ dict[str, Any]: Setup details including optional setup version.
101
+
102
+ Raises:
103
+ ValidationError: If the setup name is missing.
104
+ ServerError: If gRPC operation fails.
105
+ SetupServiceError: For any unexpected internal error.
106
+ """
107
+ with self.handle_grpc_errors("Get Setup"):
108
+ if "setup_id" not in setup_dict:
109
+ msg = "Setup name is required"
110
+ raise ValidationError(msg)
111
+ request = setup_pb2.GetSetupRequest(
112
+ setup_id=setup_dict["setup_id"],
113
+ version=setup_dict.get("version", ""),
114
+ )
115
+ response = self.exec_grpc_query("GetSetup", request)
116
+ response_data = json_format.MessageToDict(response, preserving_proto_field_name=True)
117
+ return SetupData(**response_data["setup"])
118
+
119
+ def update_setup(self, setup_dict: dict[str, Any]) -> bool:
120
+ """Update an existing setup.
121
+
122
+ Args:
123
+ setup_dict: Dictionary with setup update details.
124
+
125
+ Returns:
126
+ bool: Success status of the update operation.
127
+
128
+ Raises:
129
+ ValidationError: If setup data is invalid.
130
+ ServerError: If gRPC operation fails.
131
+ SetupServiceError: For any unexpected internal error.
132
+ """
133
+ current_setup_version = None
134
+
135
+ with self.handle_grpc_errors("Setup Update"):
136
+ valid_data = SetupData.model_validate(setup_dict)
137
+
138
+ if valid_data.current_setup_version is not None:
139
+ current_setup_version = setup_pb2.SetupVersion(**valid_data.current_setup_version.model_dump())
140
+
141
+ request = setup_pb2.UpdateSetupRequest(
142
+ setup_id=valid_data.id,
143
+ name=valid_data.name,
144
+ owner_id=valid_data.owner_id or "",
145
+ current_setup_version=current_setup_version,
146
+ )
147
+ response = self.exec_grpc_query("UpdateSetup", request)
148
+ logger.debug("Setup '%s' query sent successfully", valid_data.name)
149
+ return getattr(response, "success", False)
150
+
151
+ def delete_setup(self, setup_dict: dict[str, Any]) -> bool:
152
+ """Delete a setup by its unique identifier.
153
+
154
+ Args:
155
+ setup_dict: Dictionary with the setup 'setup_id'.
156
+
157
+ Returns:
158
+ bool: Success status of deletion.
159
+
160
+ Raises:
161
+ ValidationError: If the setup setup_id is missing.
162
+ ServerError: If gRPC operation fails.
163
+ SetupServiceError: For any unexpected internal error.
164
+ """
165
+ with self.handle_grpc_errors("Setup Deletion"):
166
+ setup_id = setup_dict.get("setup_id")
167
+ if not setup_id:
168
+ msg = "Setup name is required for deletion"
169
+ raise ValidationError(msg)
170
+ request = setup_pb2.DeleteSetupRequest(setup_id=setup_id)
171
+ response = self.exec_grpc_query("DeleteSetup", request)
172
+ logger.debug("Setup '%s' query sent successfully", setup_id)
173
+ return getattr(response, "success", False)
174
+
175
+ def create_setup_version(self, setup_version_dict: dict[str, Any]) -> str:
176
+ """Create a new setup version.
177
+
178
+ Args:
179
+ setup_version_dict: Dictionary with setup version details.
180
+
181
+ Returns:
182
+ str: version of setup version creation.
183
+
184
+ Raises:
185
+ ValidationError: If setup version data is invalid.
186
+ ServerError: If gRPC operation fails.
187
+ SetupServiceError: For any unexpected internal error.
188
+ """
189
+ with self.handle_grpc_errors("Setup Version Creation"):
190
+ valid_data = SetupVersionData.model_validate(setup_version_dict)
191
+ content_struct = Struct()
192
+ content_struct.update(valid_data.content)
193
+ request = setup_pb2.CreateSetupVersionRequest(
194
+ setup_id=valid_data.setup_id,
195
+ version=valid_data.version,
196
+ content=content_struct,
197
+ )
198
+ logger.debug(
199
+ "Setup Version '%s' for setup '%s' query sent successfully",
200
+ valid_data.version,
201
+ valid_data.setup_id,
202
+ )
203
+ return self.exec_grpc_query("CreateSetupVersion", request)
204
+
205
+ def get_setup_version(self, setup_version_dict: dict[str, Any]) -> SetupVersionData:
206
+ """Retrieve a setup version by its unique identifier.
207
+
208
+ Args:
209
+ setup_version_dict: Dictionary with the setup version 'setup_version_id'.
210
+
211
+ Returns:
212
+ dict[str, Any]: Setup version details.
213
+
214
+ Raises:
215
+ ValidationError: If the setup version id is missing.
216
+ ServerError: If gRPC operation fails.
217
+ SetupServiceError: For any unexpected internal error.
218
+ """
219
+ with self.handle_grpc_errors("Get Setup Version"):
220
+ setup_version_id = setup_version_dict.get("setup_version_id")
221
+ if not setup_version_id:
222
+ msg = "Setup version id is required"
223
+ raise ValidationError(msg)
224
+ request = setup_pb2.GetSetupVersionRequest(setup_version_id=setup_version_id)
225
+ response = self.exec_grpc_query("GetSetupVersion", request)
226
+ return SetupVersionData(
227
+ **json_format.MessageToDict(response.setup_version, preserving_proto_field_name=True)
228
+ )
229
+
230
+ def search_setup_versions(self, setup_version_dict: dict[str, Any]) -> list[SetupVersionData]:
231
+ """Search for setup versions based on filters.
232
+
233
+ Args:
234
+ setup_version_dict: Dictionary with optional 'name' and 'version' filters.
235
+
236
+ Returns:
237
+ list[dict[str, Any]]: A list of matching setup version details.
238
+
239
+ Raises:
240
+ ServerError: If gRPC operation fails.
241
+ SetupServiceError: For any unexpected internal error.
242
+ ValidationError: If both name and version are not provided.
243
+ """
244
+ with self.handle_grpc_errors("Search Setup Versions"):
245
+ if "name" not in setup_version_dict and "version" not in setup_version_dict:
246
+ msg = "Either name or version must be provided"
247
+ raise ValidationError(msg)
248
+ request = setup_pb2.SearchSetupVersionsRequest(
249
+ setup_id=setup_version_dict.get("setup_id", ""),
250
+ version=setup_version_dict.get("version", ""),
251
+ )
252
+ response = self.exec_grpc_query("SearchSetupVersions", request)
253
+ return [
254
+ SetupVersionData(**json_format.MessageToDict(sv, preserving_proto_field_name=True))
255
+ for sv in response.setup_versions
256
+ ]
257
+
258
+ def update_setup_version(self, setup_version_dict: dict[str, Any]) -> bool:
259
+ """Update an existing setup version.
260
+
261
+ Args:
262
+ setup_version_dict: Dictionary with setup version update details.
263
+
264
+ Returns:
265
+ bool: Success status of the update operation.
266
+
267
+ Raises:
268
+ ValidationError: If setup version data is invalid.
269
+ ServerError: If gRPC operation fails.
270
+ SetupServiceError: For any unexpected internal error.
271
+ """
272
+ with self.handle_grpc_errors("Setup Version Update"):
273
+ valid_data = SetupVersionData.model_validate(setup_version_dict)
274
+ content_struct = Struct()
275
+ content_struct.update(valid_data.content)
276
+ request = setup_pb2.UpdateSetupVersionRequest(
277
+ setup_version_id=valid_data.id,
278
+ version=valid_data.version,
279
+ content=content_struct,
280
+ )
281
+ response = self.exec_grpc_query("UpdateSetupVersion", request)
282
+ logger.debug(
283
+ "Setup Version '%s' for setup '%s' query sent successfully",
284
+ valid_data.id,
285
+ valid_data.setup_id,
286
+ )
287
+ return getattr(response, "success", False)
288
+
289
+ def delete_setup_version(self, setup_version_dict: dict[str, Any]) -> bool:
290
+ """Delete a setup version by its unique identifier.
291
+
292
+ Args:
293
+ setup_version_dict: Dictionary with the setup version 'name'.
294
+
295
+ Returns:
296
+ bool: Success status of version deletion.
297
+
298
+ Raises:
299
+ ValidationError: If the setup version name is missing.
300
+ ServerError: If gRPC operation fails.
301
+ SetupServiceError: For any unexpected internal error.
302
+ """
303
+ with self.handle_grpc_errors("Setup Version Deletion"):
304
+ setup_version_id = setup_version_dict.get("setup_version_id")
305
+ if not setup_version_id:
306
+ msg = "Setup version id is required for deletion"
307
+ raise ValidationError(msg)
308
+ request = setup_pb2.DeleteSetupVersionRequest(setup_version_id=setup_version_id)
309
+ response = self.exec_grpc_query("DeleteSetupVersion", request)
310
+ logger.debug("Setup Version '%s' query sent successfully", setup_version_id)
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
+ }