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,180 +0,0 @@
1
- """Factory functions for creating gRPC servers."""
2
-
3
- from pathlib import Path
4
- from typing import Any
5
-
6
- from digitalkin.grpc_servers.module_server import ModuleServer
7
- from digitalkin.grpc_servers.registry_server import RegistryServer
8
- from digitalkin.grpc_servers.utils.models import (
9
- ModuleServerConfig,
10
- RegistryServerConfig,
11
- SecurityMode,
12
- ServerCredentials,
13
- ServerMode,
14
- )
15
- from digitalkin.modules._base_module import BaseModule
16
-
17
-
18
- def create_module_server(
19
- module: type[BaseModule],
20
- host: str = "0.0.0.0", # noqa: S104
21
- port: int = 50051,
22
- max_workers: int = 10,
23
- mode: str = "sync",
24
- security: str = "insecure",
25
- registry_address: str | None = None,
26
- server_key_path: str | None = None,
27
- server_cert_path: str | None = None,
28
- root_cert_path: str | None = None,
29
- server_options: list[tuple[str, Any]] | None = None,
30
- ) -> ModuleServer:
31
- """Create a new module server.
32
-
33
- Args:
34
- module: The module to serve.
35
- host: The host address to bind to.
36
- port: The port to listen on.
37
- max_workers: Maximum number of workers for the thread pool (sync mode only).
38
- mode: Server mode ("sync" or "async").
39
- security: Security mode ("secure" or "insecure").
40
- registry_address: Optional address of a registry server for auto-registration.
41
- server_key_path: Path to server private key (required for secure mode).
42
- server_cert_path: Path to server certificate (required for secure mode).
43
- root_cert_path: Optional path to root certificate.
44
- server_options: Additional server options.
45
-
46
- Returns:
47
- A configured ModuleServer instance.
48
-
49
- Raises:
50
- ValueError: If secure mode is requested but credentials are missing.
51
- """
52
- # Create configuration with credentials if needed
53
- config = _create_server_config(
54
- ModuleServerConfig,
55
- host=host,
56
- port=port,
57
- max_workers=max_workers,
58
- mode=mode,
59
- security=security,
60
- server_key_path=server_key_path,
61
- server_cert_path=server_cert_path,
62
- root_cert_path=root_cert_path,
63
- server_options=server_options,
64
- registry_address=registry_address,
65
- )
66
-
67
- # Create and return the server
68
- return ModuleServer(module, config)
69
-
70
-
71
- def create_registry_server(
72
- host: str = "0.0.0.0", # noqa: S104
73
- port: int = 50052,
74
- max_workers: int = 10,
75
- mode: str = "sync",
76
- security: str = "insecure",
77
- database_url: str | None = None,
78
- server_key_path: str | None = None,
79
- server_cert_path: str | None = None,
80
- root_cert_path: str | None = None,
81
- server_options: list[tuple[str, Any]] | None = None,
82
- ) -> RegistryServer:
83
- """Create a new registry server.
84
-
85
- Args:
86
- host: The host address to bind to.
87
- port: The port to listen on.
88
- max_workers: Maximum number of workers for the thread pool (sync mode only).
89
- mode: Server mode ("sync" or "async").
90
- security: Security mode ("secure" or "insecure").
91
- database_url: Optional database URL for registry data storage.
92
- server_key_path: Path to server private key (required for secure mode).
93
- server_cert_path: Path to server certificate (required for secure mode).
94
- root_cert_path: Optional path to root certificate.
95
- server_options: Additional server options.
96
-
97
- Returns:
98
- A configured RegistryServer instance.
99
-
100
- Raises:
101
- ValueError: If secure mode is requested but credentials are missing.
102
- """
103
- # Create configuration with credentials if needed
104
- config = _create_server_config(
105
- RegistryServerConfig,
106
- host=host,
107
- port=port,
108
- max_workers=max_workers,
109
- mode=mode,
110
- security=security,
111
- server_key_path=server_key_path,
112
- server_cert_path=server_cert_path,
113
- root_cert_path=root_cert_path,
114
- server_options=server_options,
115
- database_url=database_url,
116
- )
117
-
118
- # Create and return the server
119
- return RegistryServer(config)
120
-
121
-
122
- def _create_server_config(
123
- config_class: Any,
124
- host: str,
125
- port: int,
126
- max_workers: int,
127
- mode: str,
128
- security: str,
129
- server_key_path: str | None,
130
- server_cert_path: str | None,
131
- root_cert_path: str | None,
132
- server_options: list[tuple[str, Any]] | None,
133
- **kwargs,
134
- ) -> ModuleServerConfig | RegistryServerConfig:
135
- """Create a server configuration with appropriate settings.
136
-
137
- Args:
138
- config_class: The configuration class to instantiate.
139
- host: The host address.
140
- port: The port number.
141
- max_workers: Maximum number of workers.
142
- mode: Server mode.
143
- security: Security mode.
144
- server_key_path: Path to server key.
145
- server_cert_path: Path to server certificate.
146
- root_cert_path: Path to root certificate.
147
- server_options: Additional server options.
148
- **kwargs: Additional configuration parameters.
149
-
150
- Returns:
151
- A server configuration instance.
152
-
153
- Raises:
154
- ValueError: If secure mode is requested but credentials are missing.
155
- """
156
- # Create basic config
157
- config_params = {
158
- "host": host,
159
- "port": port,
160
- "max_workers": max_workers,
161
- "mode": ServerMode(mode),
162
- "security": SecurityMode(security),
163
- "server_options": server_options or [],
164
- **kwargs,
165
- }
166
-
167
- # Add credentials if secure mode
168
- if security == "secure":
169
- if not server_key_path or not server_cert_path:
170
- raise ValueError(
171
- "Server key and certificate paths are required for secure mode"
172
- )
173
-
174
- config_params["credentials"] = ServerCredentials(
175
- server_key_path=Path(server_key_path),
176
- server_cert_path=Path(server_cert_path),
177
- root_cert_path=Path(root_cert_path) if root_cert_path else None,
178
- )
179
-
180
- return config_class(**config_params)
@@ -1,294 +0,0 @@
1
- """Background module manager with single instance."""
2
-
3
- import asyncio
4
- import uuid
5
- from collections.abc import AsyncGenerator, AsyncIterator
6
- from contextlib import asynccontextmanager
7
- from typing import Any, Generic
8
-
9
- import grpc
10
-
11
- from digitalkin.logger import logger
12
- from digitalkin.models import ModuleStatus
13
- from digitalkin.models.module import InputModelT, OutputModelT, SetupModelT
14
- from digitalkin.modules._base_module import BaseModule
15
- from digitalkin.modules.job_manager.base_job_manager import BaseJobManager
16
- from digitalkin.modules.job_manager.job_manager_models import StreamCodeModel
17
- from digitalkin.services.services_models import ServicesMode
18
-
19
-
20
- class SingleJobManager(BaseJobManager, Generic[InputModelT, SetupModelT]):
21
- """Manages a single instance of a module job.
22
-
23
- This class ensures that only one instance of a module job is active at a time.
24
- It provides functionality to create, stop, and monitor module jobs, as well as
25
- to handle their output data.
26
- """
27
-
28
- modules: dict[str, BaseModule]
29
- queue: dict[str, asyncio.Queue]
30
-
31
- def __init__(
32
- self,
33
- module_class: type[BaseModule],
34
- services_mode: ServicesMode,
35
- ) -> None:
36
- """Initialize the job manager.
37
-
38
- Args:
39
- module_class: The class of the module to be managed.
40
- services_mode: The mode of operation for the services (e.g., ASYNC or SYNC).
41
- """
42
- super().__init__(module_class, services_mode)
43
-
44
- self._lock = asyncio.Lock()
45
- self.modules: dict[str, BaseModule] = {}
46
- self.queues: dict[str, asyncio.Queue] = {}
47
-
48
- async def generate_config_setup_module_response(self, job_id: str) -> SetupModelT:
49
- """Generate a stream consumer for a module's output data.
50
-
51
- This method creates an asynchronous generator that streams output data
52
- from a specific module job. If the module does not exist, it generates
53
- an error message.
54
-
55
- Args:
56
- job_id: The unique identifier of the job.
57
-
58
- Returns:
59
- SetupModelT: the SetupModelT object fully processed.
60
- """
61
- module = self.modules.get(job_id, None)
62
- logger.debug("Module %s found: %s", job_id, module)
63
-
64
- try:
65
- return await self.queues[job_id].get()
66
- finally:
67
- logger.info(f"{job_id=}: {self.queues[job_id].empty()}")
68
- del self.queues[job_id]
69
-
70
- async def create_config_setup_instance_job(
71
- self,
72
- config_setup_data: SetupModelT,
73
- mission_id: str,
74
- setup_id: str,
75
- setup_version_id: str,
76
- ) -> str:
77
- """Create and start a new module setup configuration job.
78
-
79
- This method initializes a new module job, assigns it a unique job ID,
80
- and starts the config setup it in the background.
81
-
82
- Args:
83
- config_setup_data: The input data required to start the job.
84
- setup_data: The setup configuration for the module.
85
- mission_id: The mission ID associated with the job.
86
- setup_id: The setup ID associated with the module.
87
- setup_version_id: The setup ID.
88
-
89
- Returns:
90
- str: The unique identifier (job ID) of the created job.
91
-
92
- Raises:
93
- Exception: If the module fails to start.
94
- """
95
- job_id = str(uuid.uuid4())
96
- # TODO: Ensure the job_id is unique.
97
- module = self.module_class(job_id, mission_id=mission_id, setup_id=setup_id, setup_version_id=setup_version_id)
98
- self.modules[job_id] = module
99
- self.queues[job_id] = asyncio.Queue()
100
-
101
- try:
102
- await module.start_config_setup(
103
- config_setup_data,
104
- await self.job_specific_callback(self.add_to_queue, job_id),
105
- )
106
- logger.debug("Module %s (%s) started successfully", job_id, module.name)
107
- except Exception:
108
- # Remove the module from the manager in case of an error.
109
- del self.modules[job_id]
110
- logger.exception("Failed to start module %s: %s", job_id)
111
- raise
112
- else:
113
- return job_id
114
-
115
- async def add_to_queue(self, job_id: str, output_data: OutputModelT) -> None: # type: ignore
116
- """Add output data to the queue for a specific job.
117
-
118
- This method is used as a callback to handle output data generated by a module job.
119
-
120
- Args:
121
- job_id: The unique identifier of the job.
122
- output_data: The output data produced by the job.
123
- """
124
- await self.queues[job_id].put(output_data.model_dump())
125
-
126
- @asynccontextmanager # type: ignore
127
- async def generate_stream_consumer(self, job_id: str) -> AsyncIterator[AsyncGenerator[dict[str, Any], None]]: # type: ignore
128
- """Generate a stream consumer for a module's output data.
129
-
130
- This method creates an asynchronous generator that streams output data
131
- from a specific module job. If the module does not exist, it generates
132
- an error message.
133
-
134
- Args:
135
- job_id: The unique identifier of the job.
136
-
137
- Yields:
138
- AsyncGenerator: A stream of output data or error messages.
139
- """
140
- module = self.modules.get(job_id, None)
141
-
142
- logger.debug("Module %s found: %s", job_id, module)
143
-
144
- async def _stream() -> AsyncGenerator[dict[str, Any], Any]:
145
- """Stream output data from the module.
146
-
147
- Yields:
148
- dict: Output data generated by the module.
149
- """
150
- if module is None:
151
- yield {
152
- "error": {
153
- "error_message": f"Module {job_id} not found",
154
- "code": grpc.StatusCode.NOT_FOUND,
155
- }
156
- }
157
- return
158
-
159
- try:
160
- while module.status == ModuleStatus.RUNNING or (
161
- not self.queues[job_id].empty()
162
- and module.status
163
- in {
164
- ModuleStatus.STOPPED,
165
- ModuleStatus.STOPPING,
166
- }
167
- ):
168
- logger.info(f"{job_id=}: {module.status=}")
169
- yield await self.queues[job_id].get()
170
-
171
- finally:
172
- del self.queues[job_id]
173
-
174
- yield _stream()
175
-
176
- async def create_module_instance_job(
177
- self,
178
- input_data: InputModelT,
179
- setup_data: SetupModelT,
180
- mission_id: str,
181
- setup_id: str,
182
- setup_version_id: str,
183
- ) -> str:
184
- """Create and start a new module job.
185
-
186
- This method initializes a new module job, assigns it a unique job ID,
187
- and starts it in the background.
188
-
189
- Args:
190
- input_data: The input data required to start the job.
191
- setup_data: The setup configuration for the module.
192
- mission_id: The mission ID associated with the job.
193
- setup_id: The setup ID associated with the module.
194
- setup_version_id: The setup Version ID associated with the module.
195
-
196
- Returns:
197
- str: The unique identifier (job ID) of the created job.
198
-
199
- Raises:
200
- Exception: If the module fails to start.
201
- """
202
- job_id = str(uuid.uuid4())
203
- # TODO: Ensure the job_id is unique.
204
- module = self.module_class(
205
- job_id,
206
- mission_id=mission_id,
207
- setup_id=setup_id,
208
- setup_version_id=setup_version_id,
209
- )
210
- self.modules[job_id] = module
211
- self.queues[job_id] = asyncio.Queue()
212
- callback = await self.job_specific_callback(self.add_to_queue, job_id)
213
-
214
- try:
215
- await module.start(
216
- input_data,
217
- setup_data,
218
- callback,
219
- done_callback=lambda _: asyncio.create_task(callback(StreamCodeModel(code="__END_OF_STREAM__"))),
220
- )
221
- logger.debug("Module %s (%s) started successfully", job_id, module.name)
222
- except Exception:
223
- # Remove the module from the manager in case of an error.
224
- del self.modules[job_id]
225
- logger.exception("Failed to start module %s: %s", job_id)
226
- raise
227
- else:
228
- return job_id
229
-
230
- async def stop_module(self, job_id: str) -> bool:
231
- """Stop a running module job.
232
-
233
- Args:
234
- job_id: The unique identifier of the job to stop.
235
-
236
- Returns:
237
- bool: True if the module was successfully stopped, False if it does not exist.
238
-
239
- Raises:
240
- Exception: If an error occurs while stopping the module.
241
- """
242
- async with self._lock:
243
- module = self.modules.get(job_id)
244
- if not module:
245
- logger.warning(f"Module {job_id} not found")
246
- return False
247
- try:
248
- await module.stop()
249
- # should maybe be added in finally
250
- del self.queues[job_id]
251
- del self.modules[job_id]
252
- logger.debug(f"Module {job_id} ({module.name}) stopped successfully")
253
- except Exception as e:
254
- logger.error(f"Error while stopping module {job_id}: {e}")
255
- raise
256
- else:
257
- return True
258
-
259
- async def get_module_status(self, job_id: str) -> ModuleStatus | None:
260
- """Retrieve the status of a module job.
261
-
262
- Args:
263
- job_id: The unique identifier of the job.
264
-
265
- Returns:
266
- ModuleStatus | None: The status of the module, or None if it does not exist.
267
- """
268
- module = self.modules.get(job_id)
269
- return module.status if module else None
270
-
271
- async def stop_all_modules(self) -> None:
272
- """Stop all currently running module jobs.
273
-
274
- This method ensures that all active jobs are gracefully terminated.
275
- """
276
- async with self._lock:
277
- stop_tasks = [self.stop_module(job_id) for job_id in list(self.modules.keys())]
278
- if stop_tasks:
279
- await asyncio.gather(*stop_tasks, return_exceptions=True)
280
-
281
- async def list_modules(self) -> dict[str, dict[str, Any]]:
282
- """List all modules along with their statuses.
283
-
284
- Returns:
285
- dict[str, dict[str, Any]]: A dictionary containing information about all modules and their statuses.
286
- """
287
- return {
288
- job_id: {
289
- "name": module.name,
290
- "status": module.status,
291
- "class": module.__class__.__name__,
292
- }
293
- for job_id, module in self.modules.items()
294
- }