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.
- base_server/__init__.py +1 -0
- base_server/mock/__init__.py +5 -0
- base_server/mock/mock_pb2.py +39 -0
- base_server/mock/mock_pb2_grpc.py +102 -0
- base_server/server_async_insecure.py +125 -0
- base_server/server_async_secure.py +143 -0
- base_server/server_sync_insecure.py +103 -0
- base_server/server_sync_secure.py +122 -0
- digitalkin/__init__.py +8 -0
- digitalkin/__version__.py +8 -0
- digitalkin/core/__init__.py +1 -0
- digitalkin/core/common/__init__.py +9 -0
- digitalkin/core/common/factories.py +156 -0
- digitalkin/core/job_manager/__init__.py +1 -0
- digitalkin/core/job_manager/base_job_manager.py +288 -0
- digitalkin/core/job_manager/single_job_manager.py +354 -0
- digitalkin/core/job_manager/taskiq_broker.py +311 -0
- digitalkin/core/job_manager/taskiq_job_manager.py +541 -0
- digitalkin/core/task_manager/__init__.py +1 -0
- digitalkin/core/task_manager/base_task_manager.py +539 -0
- digitalkin/core/task_manager/local_task_manager.py +108 -0
- digitalkin/core/task_manager/remote_task_manager.py +87 -0
- digitalkin/core/task_manager/surrealdb_repository.py +266 -0
- digitalkin/core/task_manager/task_executor.py +249 -0
- digitalkin/core/task_manager/task_session.py +406 -0
- digitalkin/grpc_servers/__init__.py +1 -0
- digitalkin/grpc_servers/_base_server.py +486 -0
- digitalkin/grpc_servers/module_server.py +208 -0
- digitalkin/grpc_servers/module_servicer.py +516 -0
- digitalkin/grpc_servers/utils/__init__.py +1 -0
- digitalkin/grpc_servers/utils/exceptions.py +29 -0
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +88 -0
- digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
- digitalkin/grpc_servers/utils/utility_schema_extender.py +97 -0
- digitalkin/logger.py +157 -0
- digitalkin/mixins/__init__.py +19 -0
- digitalkin/mixins/base_mixin.py +10 -0
- digitalkin/mixins/callback_mixin.py +24 -0
- digitalkin/mixins/chat_history_mixin.py +110 -0
- digitalkin/mixins/cost_mixin.py +76 -0
- digitalkin/mixins/file_history_mixin.py +93 -0
- digitalkin/mixins/filesystem_mixin.py +46 -0
- digitalkin/mixins/logger_mixin.py +51 -0
- digitalkin/mixins/storage_mixin.py +79 -0
- digitalkin/models/__init__.py +8 -0
- digitalkin/models/core/__init__.py +1 -0
- digitalkin/models/core/job_manager_models.py +36 -0
- digitalkin/models/core/task_monitor.py +70 -0
- digitalkin/models/grpc_servers/__init__.py +1 -0
- digitalkin/models/grpc_servers/models.py +275 -0
- digitalkin/models/grpc_servers/types.py +24 -0
- digitalkin/models/module/__init__.py +25 -0
- digitalkin/models/module/module.py +40 -0
- digitalkin/models/module/module_context.py +149 -0
- digitalkin/models/module/module_types.py +393 -0
- digitalkin/models/module/utility.py +146 -0
- digitalkin/models/services/__init__.py +10 -0
- digitalkin/models/services/cost.py +54 -0
- digitalkin/models/services/registry.py +42 -0
- digitalkin/models/services/storage.py +44 -0
- digitalkin/modules/__init__.py +11 -0
- digitalkin/modules/_base_module.py +517 -0
- digitalkin/modules/archetype_module.py +23 -0
- digitalkin/modules/tool_module.py +23 -0
- digitalkin/modules/trigger_handler.py +48 -0
- digitalkin/modules/triggers/__init__.py +12 -0
- digitalkin/modules/triggers/healthcheck_ping_trigger.py +45 -0
- digitalkin/modules/triggers/healthcheck_services_trigger.py +63 -0
- digitalkin/modules/triggers/healthcheck_status_trigger.py +52 -0
- digitalkin/py.typed +0 -0
- digitalkin/services/__init__.py +30 -0
- digitalkin/services/agent/__init__.py +6 -0
- digitalkin/services/agent/agent_strategy.py +19 -0
- digitalkin/services/agent/default_agent.py +13 -0
- digitalkin/services/base_strategy.py +22 -0
- digitalkin/services/communication/__init__.py +7 -0
- digitalkin/services/communication/communication_strategy.py +76 -0
- digitalkin/services/communication/default_communication.py +101 -0
- digitalkin/services/communication/grpc_communication.py +223 -0
- digitalkin/services/cost/__init__.py +14 -0
- digitalkin/services/cost/cost_strategy.py +100 -0
- digitalkin/services/cost/default_cost.py +114 -0
- digitalkin/services/cost/grpc_cost.py +138 -0
- digitalkin/services/filesystem/__init__.py +7 -0
- digitalkin/services/filesystem/default_filesystem.py +417 -0
- digitalkin/services/filesystem/filesystem_strategy.py +252 -0
- digitalkin/services/filesystem/grpc_filesystem.py +317 -0
- digitalkin/services/identity/__init__.py +6 -0
- digitalkin/services/identity/default_identity.py +15 -0
- digitalkin/services/identity/identity_strategy.py +14 -0
- digitalkin/services/registry/__init__.py +27 -0
- digitalkin/services/registry/default_registry.py +141 -0
- digitalkin/services/registry/exceptions.py +47 -0
- digitalkin/services/registry/grpc_registry.py +306 -0
- digitalkin/services/registry/registry_models.py +43 -0
- digitalkin/services/registry/registry_strategy.py +98 -0
- digitalkin/services/services_config.py +200 -0
- digitalkin/services/services_models.py +65 -0
- digitalkin/services/setup/__init__.py +1 -0
- digitalkin/services/setup/default_setup.py +219 -0
- digitalkin/services/setup/grpc_setup.py +343 -0
- digitalkin/services/setup/setup_strategy.py +145 -0
- digitalkin/services/snapshot/__init__.py +6 -0
- digitalkin/services/snapshot/default_snapshot.py +39 -0
- digitalkin/services/snapshot/snapshot_strategy.py +30 -0
- digitalkin/services/storage/__init__.py +7 -0
- digitalkin/services/storage/default_storage.py +228 -0
- digitalkin/services/storage/grpc_storage.py +214 -0
- digitalkin/services/storage/storage_strategy.py +273 -0
- digitalkin/services/user_profile/__init__.py +12 -0
- digitalkin/services/user_profile/default_user_profile.py +55 -0
- digitalkin/services/user_profile/grpc_user_profile.py +69 -0
- digitalkin/services/user_profile/user_profile_strategy.py +40 -0
- digitalkin/utils/__init__.py +29 -0
- digitalkin/utils/arg_parser.py +92 -0
- digitalkin/utils/development_mode_action.py +51 -0
- digitalkin/utils/dynamic_schema.py +483 -0
- digitalkin/utils/llm_ready_schema.py +75 -0
- digitalkin/utils/package_discover.py +357 -0
- digitalkin-0.3.2.dev2.dist-info/METADATA +602 -0
- digitalkin-0.3.2.dev2.dist-info/RECORD +131 -0
- digitalkin-0.3.2.dev2.dist-info/WHEEL +5 -0
- digitalkin-0.3.2.dev2.dist-info/licenses/LICENSE +430 -0
- digitalkin-0.3.2.dev2.dist-info/top_level.txt +4 -0
- modules/__init__.py +0 -0
- modules/cpu_intensive_module.py +280 -0
- modules/dynamic_setup_module.py +338 -0
- modules/minimal_llm_module.py +347 -0
- modules/text_transform_module.py +203 -0
- services/filesystem_module.py +200 -0
- services/storage_module.py +206 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""gRPC filesystem implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from agentic_mesh_protocol.filesystem.v1 import filesystem_pb2, filesystem_service_pb2_grpc
|
|
6
|
+
from google.protobuf import struct_pb2
|
|
7
|
+
from google.protobuf.json_format import MessageToDict
|
|
8
|
+
|
|
9
|
+
from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
|
|
10
|
+
from digitalkin.grpc_servers.utils.grpc_error_handler import GrpcErrorHandlerMixin
|
|
11
|
+
from digitalkin.logger import logger
|
|
12
|
+
from digitalkin.models.grpc_servers.models import ClientConfig
|
|
13
|
+
from digitalkin.services.filesystem.filesystem_strategy import (
|
|
14
|
+
FileFilter,
|
|
15
|
+
FilesystemRecord,
|
|
16
|
+
FilesystemServiceError,
|
|
17
|
+
FilesystemStrategy,
|
|
18
|
+
UploadFileData,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper, GrpcErrorHandlerMixin):
|
|
23
|
+
"""Default state filesystem strategy."""
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _file_type_to_enum(file_type: str) -> filesystem_pb2.FileType:
|
|
27
|
+
"""Convert a file type string to a FileType enum.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
file_type: The file type string to convert
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
filesystem_pb2.FileType: The converted file type enum
|
|
34
|
+
"""
|
|
35
|
+
if not file_type.upper().startswith("FILE_TYPE_"):
|
|
36
|
+
file_type = f"FILE_TYPE_{file_type.upper()}"
|
|
37
|
+
try:
|
|
38
|
+
return getattr(filesystem_pb2.FileType, file_type.upper())
|
|
39
|
+
except AttributeError:
|
|
40
|
+
return filesystem_pb2.FileType.FILE_TYPE_UNSPECIFIED
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _file_status_to_enum(file_status: str) -> filesystem_pb2.FileStatus:
|
|
44
|
+
"""Convert a file status string to a FileStatus enum.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
file_status: The file status string to convert
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
filesystem_pb2.FileStatus: The converted file status enum
|
|
51
|
+
"""
|
|
52
|
+
if not file_status.upper().startswith("FILE_STATUS_"):
|
|
53
|
+
file_status = f"FILE_STATUS_{file_status.upper()}"
|
|
54
|
+
try:
|
|
55
|
+
return getattr(filesystem_pb2.FileStatus, file_status.upper())
|
|
56
|
+
except AttributeError:
|
|
57
|
+
return filesystem_pb2.FileStatus.FILE_STATUS_UNSPECIFIED
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _file_proto_to_data(file: filesystem_pb2.File) -> FilesystemRecord:
|
|
61
|
+
"""Convert a File proto message to FilesystemRecord.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
file: The File proto message to convert
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
FilesystemRecord: The converted data
|
|
68
|
+
"""
|
|
69
|
+
return FilesystemRecord(
|
|
70
|
+
id=file.file_id,
|
|
71
|
+
context=file.context,
|
|
72
|
+
name=file.name,
|
|
73
|
+
file_type=filesystem_pb2.FileType.Name(file.file_type),
|
|
74
|
+
content_type=file.content_type,
|
|
75
|
+
size_bytes=file.size_bytes,
|
|
76
|
+
checksum=file.checksum,
|
|
77
|
+
metadata=MessageToDict(file.metadata),
|
|
78
|
+
storage_uri=file.storage_uri,
|
|
79
|
+
file_url=file.file_url,
|
|
80
|
+
status=filesystem_pb2.FileStatus.Name(file.status),
|
|
81
|
+
content=file.content,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def _filter_to_proto(self, filters: FileFilter) -> filesystem_pb2.FileFilter:
|
|
85
|
+
"""Convert a FileFilter to a FileFilter proto message.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
filters: The FileFilter to convert
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
filesystem_pb2.FileFilter: The converted FileFilter proto message
|
|
92
|
+
"""
|
|
93
|
+
return filesystem_pb2.FileFilter(
|
|
94
|
+
**filters.model_dump(exclude={"file_types", "status"}),
|
|
95
|
+
file_types=[self._file_type_to_enum(file_type) for file_type in filters.file_types]
|
|
96
|
+
if filters.file_types
|
|
97
|
+
else None,
|
|
98
|
+
status=self._file_status_to_enum(filters.status) if filters.status else None,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
mission_id: str,
|
|
104
|
+
setup_id: str,
|
|
105
|
+
setup_version_id: str,
|
|
106
|
+
client_config: ClientConfig,
|
|
107
|
+
config: dict[str, Any] | None = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Initialize the gRPC filesystem strategy.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
mission_id: The ID of the mission this strategy is associated with
|
|
113
|
+
setup_id: The ID of the setup
|
|
114
|
+
setup_version_id: The ID of the setup version this strategy is associated with
|
|
115
|
+
client_config: Configuration for the gRPC client connection
|
|
116
|
+
config: Configuration for the filesystem strategy
|
|
117
|
+
"""
|
|
118
|
+
super().__init__(mission_id, setup_id, setup_version_id, config)
|
|
119
|
+
self.service_name = "FilesystemService"
|
|
120
|
+
channel = self._init_channel(client_config)
|
|
121
|
+
self.stub = filesystem_service_pb2_grpc.FilesystemServiceStub(channel)
|
|
122
|
+
logger.debug("Channel client 'Filesystem' initialized successfully")
|
|
123
|
+
|
|
124
|
+
def upload_files(
|
|
125
|
+
self,
|
|
126
|
+
files: list[UploadFileData],
|
|
127
|
+
) -> tuple[list[FilesystemRecord], int, int]:
|
|
128
|
+
"""Upload multiple files to the filesystem.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
files: List of tuples containing (content, name, file_type, content_type, metadata, replace_if_exists)
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
tuple[list[FilesystemRecord], int, int]: List of uploaded files, total uploaded count, total failed count
|
|
135
|
+
"""
|
|
136
|
+
logger.debug("Uploading %d files", len(files))
|
|
137
|
+
with self.handle_grpc_errors("UploadFiles", FilesystemServiceError):
|
|
138
|
+
upload_files: list[filesystem_pb2.UploadFileData] = []
|
|
139
|
+
for file in files:
|
|
140
|
+
metadata_struct: struct_pb2.Struct | None = None
|
|
141
|
+
if file.metadata:
|
|
142
|
+
metadata_struct = struct_pb2.Struct()
|
|
143
|
+
metadata_struct.update(file.metadata)
|
|
144
|
+
upload_files.append(
|
|
145
|
+
filesystem_pb2.UploadFileData(
|
|
146
|
+
context=self.mission_id,
|
|
147
|
+
name=file.name,
|
|
148
|
+
file_type=self._file_type_to_enum(file.file_type),
|
|
149
|
+
content_type=file.content_type or "application/octet-stream",
|
|
150
|
+
content=file.content,
|
|
151
|
+
metadata=metadata_struct,
|
|
152
|
+
status=filesystem_pb2.FileStatus.FILE_STATUS_UPLOADING,
|
|
153
|
+
replace_if_exists=file.replace_if_exists,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
request = filesystem_pb2.UploadFilesRequest(files=upload_files)
|
|
157
|
+
response: filesystem_pb2.UploadFilesResponse = self.exec_grpc_query("UploadFiles", request)
|
|
158
|
+
results = [self._file_proto_to_data(result.file) for result in response.results if result.HasField("file")]
|
|
159
|
+
logger.debug("Uploaded files: %s", results)
|
|
160
|
+
return results, response.total_uploaded, response.total_failed
|
|
161
|
+
|
|
162
|
+
def get_file(
|
|
163
|
+
self,
|
|
164
|
+
file_id: str,
|
|
165
|
+
context: Literal["mission", "setup"] = "mission",
|
|
166
|
+
*,
|
|
167
|
+
include_content: bool = False,
|
|
168
|
+
) -> FilesystemRecord:
|
|
169
|
+
"""Get a file from the filesystem.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
file_id: The ID of the file to be retrieved
|
|
173
|
+
context: The context of the files (mission or setup)
|
|
174
|
+
include_content: Whether to include file content in response
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
FilesystemRecord: Metadata about the retrieved file
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
FilesystemServiceError: If there is an error retrieving the file
|
|
181
|
+
"""
|
|
182
|
+
match context:
|
|
183
|
+
case "setup":
|
|
184
|
+
context_id = self.setup_id
|
|
185
|
+
case "mission":
|
|
186
|
+
context_id = self.mission_id
|
|
187
|
+
with self.handle_grpc_errors("GetFile", FilesystemServiceError):
|
|
188
|
+
request = filesystem_pb2.GetFileRequest(
|
|
189
|
+
context=context_id,
|
|
190
|
+
file_id=file_id,
|
|
191
|
+
include_content=include_content,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
response: filesystem_pb2.GetFileResponse = self.exec_grpc_query("GetFile", request)
|
|
195
|
+
|
|
196
|
+
return self._file_proto_to_data(response.file)
|
|
197
|
+
|
|
198
|
+
def update_file(
|
|
199
|
+
self,
|
|
200
|
+
file_id: str,
|
|
201
|
+
content: bytes | None = None,
|
|
202
|
+
file_type: Literal[
|
|
203
|
+
"UNSPECIFIED",
|
|
204
|
+
"DOCUMENT",
|
|
205
|
+
"IMAGE",
|
|
206
|
+
"VIDEO",
|
|
207
|
+
"AUDIO",
|
|
208
|
+
"ARCHIVE",
|
|
209
|
+
"CODE",
|
|
210
|
+
"OTHER",
|
|
211
|
+
]
|
|
212
|
+
| None = None,
|
|
213
|
+
content_type: str | None = None,
|
|
214
|
+
metadata: dict[str, Any] | None = None,
|
|
215
|
+
new_name: str | None = None,
|
|
216
|
+
status: str | None = None,
|
|
217
|
+
) -> FilesystemRecord:
|
|
218
|
+
"""Update a file in the filesystem.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
file_id: The id of the file to be updated
|
|
222
|
+
content: Optional new content of the file
|
|
223
|
+
file_type: Optional new type of data
|
|
224
|
+
content_type: Optional new MIME type
|
|
225
|
+
metadata: Optional new metadata (will merge with existing)
|
|
226
|
+
new_name: Optional new name for the file
|
|
227
|
+
status: Optional new status for the file
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
FilesystemRecord: Metadata about the updated file
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
FilesystemServiceError: If there is an error during update
|
|
234
|
+
"""
|
|
235
|
+
with self.handle_grpc_errors("UpdateFile", FilesystemServiceError):
|
|
236
|
+
request = filesystem_pb2.UpdateFileRequest(
|
|
237
|
+
context=self.mission_id,
|
|
238
|
+
file_id=file_id,
|
|
239
|
+
content=content,
|
|
240
|
+
file_type=self._file_type_to_enum(file_type) if file_type else None,
|
|
241
|
+
content_type=content_type,
|
|
242
|
+
new_name=new_name,
|
|
243
|
+
status=self._file_status_to_enum(status) if status else None,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
if metadata:
|
|
247
|
+
request.metadata.update(metadata)
|
|
248
|
+
|
|
249
|
+
response: filesystem_pb2.UpdateFileResponse = self.exec_grpc_query("UpdateFile", request)
|
|
250
|
+
return self._file_proto_to_data(response.result.file)
|
|
251
|
+
|
|
252
|
+
def delete_files(
|
|
253
|
+
self,
|
|
254
|
+
filters: FileFilter,
|
|
255
|
+
*,
|
|
256
|
+
permanent: bool = False,
|
|
257
|
+
force: bool = False,
|
|
258
|
+
) -> tuple[dict[str, bool], int, int]:
|
|
259
|
+
"""Delete multiple files from the filesystem.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
filters: Filter criteria for the files
|
|
263
|
+
permanent: Whether to permanently delete the files
|
|
264
|
+
force: Whether to force delete even if files are in use
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
tuple[dict[str, bool], int, int]: Results per file, total deleted count, total failed count
|
|
268
|
+
"""
|
|
269
|
+
with self.handle_grpc_errors("DeleteFiles", FilesystemServiceError):
|
|
270
|
+
request = filesystem_pb2.DeleteFilesRequest(
|
|
271
|
+
context=self.mission_id,
|
|
272
|
+
filters=self._filter_to_proto(filters),
|
|
273
|
+
permanent=permanent,
|
|
274
|
+
force=force,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
response: filesystem_pb2.DeleteFilesResponse = self.exec_grpc_query("DeleteFiles", request)
|
|
278
|
+
return dict(response.results), response.total_deleted, response.total_failed
|
|
279
|
+
|
|
280
|
+
def get_files(
|
|
281
|
+
self,
|
|
282
|
+
filters: FileFilter,
|
|
283
|
+
*,
|
|
284
|
+
list_size: int = 100,
|
|
285
|
+
offset: int = 0,
|
|
286
|
+
order: str | None = None,
|
|
287
|
+
include_content: bool = False,
|
|
288
|
+
) -> tuple[list[FilesystemRecord], int]:
|
|
289
|
+
"""Get multiple files from the filesystem.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
filters: Filter criteria for the files
|
|
293
|
+
list_size: Number of files to return per page
|
|
294
|
+
offset: Offset to start from
|
|
295
|
+
order: Field to order results by
|
|
296
|
+
include_content: Whether to include file content in response
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
tuple[list[FilesystemRecord], int]: List of files and total count
|
|
300
|
+
"""
|
|
301
|
+
match filters.context:
|
|
302
|
+
case "setup":
|
|
303
|
+
context_id = self.setup_id
|
|
304
|
+
case "mission":
|
|
305
|
+
context_id = self.mission_id
|
|
306
|
+
with self.handle_grpc_errors("GetFiles", FilesystemServiceError):
|
|
307
|
+
request = filesystem_pb2.GetFilesRequest(
|
|
308
|
+
context=context_id,
|
|
309
|
+
filters=self._filter_to_proto(filters),
|
|
310
|
+
include_content=include_content,
|
|
311
|
+
list_size=list_size,
|
|
312
|
+
offset=offset,
|
|
313
|
+
order=order,
|
|
314
|
+
)
|
|
315
|
+
response: filesystem_pb2.GetFilesResponse = self.exec_grpc_query("GetFiles", request)
|
|
316
|
+
|
|
317
|
+
return [self._file_proto_to_data(file) for file in response.files], response.total_count
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""This module is responsible for handling the identity service."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.services.identity.default_identity import DefaultIdentity
|
|
4
|
+
from digitalkin.services.identity.identity_strategy import IdentityStrategy
|
|
5
|
+
|
|
6
|
+
__all__ = ["DefaultIdentity", "IdentityStrategy"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Default identity."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.services.identity.identity_strategy import IdentityStrategy
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DefaultIdentity(IdentityStrategy):
|
|
7
|
+
"""DefaultIdentity is the default identity strategy."""
|
|
8
|
+
|
|
9
|
+
async def get_identity(self) -> str: # noqa: PLR6301
|
|
10
|
+
"""Get the identity.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
str: The identity
|
|
14
|
+
"""
|
|
15
|
+
return "default_identity"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""This module contains the abstract base class for identity strategies."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from digitalkin.services.base_strategy import BaseStrategy
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IdentityStrategy(BaseStrategy, ABC):
|
|
9
|
+
"""IdentityStrategy is the abstract base class for all identity strategies."""
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
async def get_identity(self) -> str:
|
|
13
|
+
"""Get the identity."""
|
|
14
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""This module is responsible for handling the registry service."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.models.services.registry import (
|
|
4
|
+
ModuleInfo,
|
|
5
|
+
ModuleStatusInfo,
|
|
6
|
+
RegistryModuleStatus,
|
|
7
|
+
RegistryModuleType,
|
|
8
|
+
)
|
|
9
|
+
from digitalkin.services.registry.default_registry import DefaultRegistry
|
|
10
|
+
from digitalkin.services.registry.exceptions import (
|
|
11
|
+
RegistryModuleNotFoundError,
|
|
12
|
+
RegistryServiceError,
|
|
13
|
+
)
|
|
14
|
+
from digitalkin.services.registry.grpc_registry import GrpcRegistry
|
|
15
|
+
from digitalkin.services.registry.registry_strategy import RegistryStrategy
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"DefaultRegistry",
|
|
19
|
+
"GrpcRegistry",
|
|
20
|
+
"ModuleInfo",
|
|
21
|
+
"ModuleStatusInfo",
|
|
22
|
+
"RegistryModuleNotFoundError",
|
|
23
|
+
"RegistryModuleStatus",
|
|
24
|
+
"RegistryModuleType",
|
|
25
|
+
"RegistryServiceError",
|
|
26
|
+
"RegistryStrategy",
|
|
27
|
+
]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Default registry implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from digitalkin.models.services.registry import (
|
|
6
|
+
ModuleInfo,
|
|
7
|
+
ModuleStatusInfo,
|
|
8
|
+
RegistryModuleStatus,
|
|
9
|
+
RegistryModuleType,
|
|
10
|
+
)
|
|
11
|
+
from digitalkin.services.registry.exceptions import RegistryModuleNotFoundError
|
|
12
|
+
from digitalkin.services.registry.registry_strategy import RegistryStrategy
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DefaultRegistry(RegistryStrategy):
|
|
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)
|
|
129
|
+
|
|
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}")
|