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,156 @@
1
+ """Common factory functions for reducing code duplication in core module."""
2
+
3
+ import asyncio
4
+ import datetime
5
+
6
+ from digitalkin.core.task_manager.surrealdb_repository import SurrealDBConnection
7
+ from digitalkin.logger import logger
8
+ from digitalkin.modules._base_module import BaseModule
9
+
10
+
11
+ class ConnectionFactory:
12
+ """Factory for creating SurrealDB connections with consistent configuration."""
13
+
14
+ @staticmethod
15
+ async def create_surreal_connection(
16
+ database: str = "task_manager",
17
+ timeout: datetime.timedelta = datetime.timedelta(seconds=5),
18
+ *,
19
+ auto_init: bool = True,
20
+ ) -> SurrealDBConnection:
21
+ """Create and optionally initialize a SurrealDB connection.
22
+
23
+ This factory method centralizes the creation of SurrealDB connections
24
+ to ensure consistent configuration across the codebase.
25
+
26
+ Args:
27
+ database: Database name to connect to
28
+ timeout: Connection timeout
29
+ auto_init: Whether to automatically initialize the connection
30
+
31
+ Returns:
32
+ Initialized or uninitialized SurrealDBConnection instance
33
+
34
+ Example:
35
+ # Create and auto-initialize
36
+ conn = await ConnectionFactory.create_surreal_connection("taskiq_worker")
37
+
38
+ # Create without initialization
39
+ conn = await ConnectionFactory.create_surreal_connection(auto_init=False)
40
+ await conn.init_surreal_instance()
41
+ """
42
+ logger.debug(
43
+ "Creating SurrealDB connection for database: %s, timeout: %s",
44
+ database,
45
+ timeout,
46
+ extra={"database": database, "timeout": str(timeout)},
47
+ )
48
+
49
+ connection: SurrealDBConnection = SurrealDBConnection(database, timeout)
50
+
51
+ if auto_init:
52
+ await connection.init_surreal_instance()
53
+ logger.debug("SurrealDB connection initialized for database: %s", database)
54
+
55
+ return connection
56
+
57
+
58
+ class ModuleFactory:
59
+ """Factory for creating module instances with consistent configuration."""
60
+
61
+ @staticmethod
62
+ def create_module_instance(
63
+ module_class: type[BaseModule],
64
+ job_id: str,
65
+ mission_id: str,
66
+ setup_id: str,
67
+ setup_version_id: str,
68
+ ) -> BaseModule:
69
+ """Create a module instance with standard parameters.
70
+
71
+ This factory method centralizes module instantiation to ensure
72
+ consistent parameter passing across the codebase.
73
+
74
+ Args:
75
+ module_class: The module class to instantiate
76
+ job_id: Unique job identifier
77
+ mission_id: Mission identifier
78
+ setup_id: Setup identifier
79
+ setup_version_id: Setup version identifier
80
+
81
+ Returns:
82
+ Instantiated module
83
+
84
+ Raises:
85
+ ValueError: If job_id or mission_id is empty
86
+
87
+ Example:
88
+ module = ModuleFactory.create_module_instance(
89
+ MyModule,
90
+ job_id="job_123",
91
+ mission_id="mission:test",
92
+ setup_id="setup:config",
93
+ setup_version_id="v1.0",
94
+ )
95
+ """
96
+ # Validate parameters
97
+ if not job_id:
98
+ msg = "job_id cannot be empty"
99
+ raise ValueError(msg)
100
+ if not mission_id:
101
+ msg = "mission_id cannot be empty"
102
+ raise ValueError(msg)
103
+
104
+ logger.debug(
105
+ "Creating module instance: %s for job: %s",
106
+ module_class.__name__,
107
+ job_id,
108
+ extra={
109
+ "module_class": module_class.__name__,
110
+ "job_id": job_id,
111
+ "mission_id": mission_id,
112
+ "setup_id": setup_id,
113
+ "setup_version_id": setup_version_id,
114
+ },
115
+ )
116
+
117
+ return module_class(
118
+ job_id=job_id,
119
+ mission_id=mission_id,
120
+ setup_id=setup_id,
121
+ setup_version_id=setup_version_id,
122
+ )
123
+
124
+
125
+ class QueueFactory:
126
+ """Factory for creating asyncio queues with consistent configuration."""
127
+
128
+ # Default max queue size to prevent unbounded memory growth
129
+ DEFAULT_MAX_QUEUE_SIZE = 1000
130
+
131
+ @staticmethod
132
+ def create_bounded_queue(maxsize: int = DEFAULT_MAX_QUEUE_SIZE) -> asyncio.Queue:
133
+ """Create a bounded asyncio queue with standard configuration.
134
+
135
+ Args:
136
+ maxsize: Maximum queue size (default 1000, 0 means unlimited)
137
+
138
+ Returns:
139
+ Bounded asyncio.Queue instance
140
+
141
+ Raises:
142
+ ValueError: If maxsize is negative
143
+
144
+ Example:
145
+ queue = QueueFactory.create_bounded_queue()
146
+ # or with custom size
147
+ queue = QueueFactory.create_bounded_queue(maxsize=500)
148
+ # unlimited queue
149
+ queue = QueueFactory.create_bounded_queue(maxsize=0)
150
+ """
151
+ if maxsize < 0:
152
+ msg = "maxsize must be >= 0"
153
+ raise ValueError(msg)
154
+
155
+ logger.debug("Creating bounded queue with maxsize: %d", maxsize, extra={"maxsize": maxsize})
156
+ return asyncio.Queue(maxsize=maxsize)
@@ -0,0 +1 @@
1
+ """Job Manager logic."""
@@ -0,0 +1,288 @@
1
+ """Background module manager."""
2
+
3
+ import abc
4
+ from collections.abc import AsyncGenerator, AsyncIterator, Callable, Coroutine
5
+ from contextlib import asynccontextmanager
6
+ from typing import Any, Generic
7
+
8
+ from digitalkin.core.task_manager.base_task_manager import BaseTaskManager
9
+ from digitalkin.core.task_manager.task_session import TaskSession
10
+ from digitalkin.models.core.task_monitor import TaskStatus
11
+ from digitalkin.models.module.module import ModuleCodeModel
12
+ from digitalkin.models.module.module_types import InputModelT, OutputModelT, SetupModelT
13
+ from digitalkin.modules._base_module import BaseModule
14
+ from digitalkin.services.services_config import ServicesConfig
15
+ from digitalkin.services.services_models import ServicesMode
16
+
17
+
18
+ class BaseJobManager(abc.ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
19
+ """Abstract base class for managing background module jobs.
20
+
21
+ Uses composition to delegate task lifecycle management to a TaskManager.
22
+ """
23
+
24
+ module_class: type[BaseModule]
25
+ services_mode: ServicesMode
26
+ _task_manager: BaseTaskManager
27
+
28
+ def __init__(
29
+ self,
30
+ module_class: type[BaseModule],
31
+ services_mode: ServicesMode,
32
+ task_manager: BaseTaskManager,
33
+ ) -> None:
34
+ """Initialize the job manager.
35
+
36
+ Args:
37
+ module_class: The class of the module to be managed.
38
+ services_mode: The mode of operation for the services (e.g., ASYNC or SYNC).
39
+ task_manager: The task manager instance to use for task lifecycle management.
40
+ """
41
+ self.module_class = module_class
42
+ self.services_mode = services_mode
43
+ self._task_manager = task_manager
44
+
45
+ services_config = ServicesConfig(
46
+ services_config_strategies=self.module_class.services_config_strategies,
47
+ services_config_params=self.module_class.services_config_params,
48
+ mode=services_mode,
49
+ )
50
+ setattr(self.module_class, "services_config", services_config)
51
+
52
+ # Properties to expose task manager attributes
53
+ @property
54
+ def tasks_sessions(self) -> dict[str, TaskSession]:
55
+ """Get task sessions from the task manager."""
56
+ return self._task_manager.tasks_sessions
57
+
58
+ @property
59
+ def tasks(self) -> dict[str, Any]:
60
+ """Get tasks from the task manager."""
61
+ return self._task_manager.tasks
62
+
63
+ # Delegate task lifecycle methods to task manager
64
+ async def create_task(
65
+ self,
66
+ task_id: str,
67
+ mission_id: str,
68
+ module: BaseModule,
69
+ coro: Coroutine[Any, Any, None],
70
+ **kwargs: Any, # noqa: ANN401
71
+ ) -> None:
72
+ """Create a task using the task manager.
73
+
74
+ Args:
75
+ task_id: Unique identifier for the task
76
+ mission_id: Mission identifier
77
+ module: Module instance
78
+ coro: Coroutine to execute
79
+ **kwargs: Additional arguments for task creation
80
+ """
81
+ await self._task_manager.create_task(task_id, mission_id, module, coro, **kwargs)
82
+
83
+ async def clean_session(self, task_id: str, mission_id: str) -> bool:
84
+ """Clean a task's session.
85
+
86
+ Args:
87
+ task_id: Unique identifier for the task.
88
+ mission_id: Mission identifier.
89
+
90
+ Returns:
91
+ bool: True if the task was successfully cancelled, False otherwise.
92
+ """
93
+ return await self._task_manager.clean_session(task_id, mission_id)
94
+
95
+ async def cancel_task(self, task_id: str, mission_id: str, timeout: float | None = None) -> bool:
96
+ """Cancel a task.
97
+
98
+ Args:
99
+ task_id: Unique identifier for the task.
100
+ mission_id: Mission identifier.
101
+ timeout: Optional timeout in seconds to wait for the cancellation to complete.
102
+
103
+ Returns:
104
+ bool: True if the task was successfully cancelled, False otherwise.
105
+ """
106
+ return await self._task_manager.cancel_task(task_id, mission_id, timeout)
107
+
108
+ async def send_signal(self, task_id: str, mission_id: str, signal_type: str, payload: dict) -> bool:
109
+ """Send signal to a task.
110
+
111
+ Args:
112
+ task_id: Unique identifier for the task.
113
+ mission_id: Mission identifier.
114
+ signal_type: Type of signal to send.
115
+ payload: Payload data for the signal.
116
+
117
+ Returns:
118
+ bool: True if the signal was successfully sent, False otherwise.
119
+ """
120
+ return await self._task_manager.send_signal(task_id, mission_id, signal_type, payload)
121
+
122
+ async def shutdown(self, mission_id: str, timeout: float = 30.0) -> None:
123
+ """Shutdown all tasks."""
124
+ await self._task_manager.shutdown(mission_id, timeout)
125
+
126
+ @abc.abstractmethod
127
+ async def start(self) -> None:
128
+ """Start the job manager.
129
+
130
+ This method initializes any necessary resources or configurations
131
+ required for the job manager to function.
132
+ """
133
+
134
+ @staticmethod
135
+ async def job_specific_callback(
136
+ callback: Callable[[str, OutputModelT | ModuleCodeModel], Coroutine[Any, Any, None]],
137
+ job_id: str,
138
+ ) -> Callable[[OutputModelT | ModuleCodeModel], Coroutine[Any, Any, None]]:
139
+ """Generate a job-specific callback function.
140
+
141
+ Args:
142
+ callback: The callback function to be executed when the job completes.
143
+ job_id: The unique identifier of the job.
144
+
145
+ Returns:
146
+ Callable: A wrapped callback function that includes the job ID.
147
+ """
148
+
149
+ def callback_wrapper(output_data: OutputModelT | ModuleCodeModel) -> Coroutine[Any, Any, None]:
150
+ """Wrapper for the callback function.
151
+
152
+ Args:
153
+ output_data: The output data produced by the job.
154
+
155
+ Returns:
156
+ Coroutine: The wrapped callback function.
157
+ """
158
+ return callback(job_id, output_data)
159
+
160
+ return callback_wrapper
161
+
162
+ @abc.abstractmethod # type: ignore
163
+ @asynccontextmanager # type: ignore
164
+ async def generate_stream_consumer(self, job_id: str) -> AsyncIterator[AsyncGenerator[dict[str, Any], None]]:
165
+ """Generate a stream consumer for the job's message stream.
166
+
167
+ Args:
168
+ job_id: The unique identifier of the job to filter messages for.
169
+
170
+ Yields:
171
+ dict[str, Any]: The messages from the associated module's stream.
172
+ """
173
+
174
+ @abc.abstractmethod
175
+ async def create_module_instance_job(
176
+ self,
177
+ input_data: InputModelT,
178
+ setup_data: SetupModelT,
179
+ mission_id: str,
180
+ setup_id: str,
181
+ setup_version_id: str,
182
+ ) -> str:
183
+ """Create and start a new job for the module's instance.
184
+
185
+ Args:
186
+ input_data: The input data required to start the job.
187
+ setup_data: The setup configuration for the module.
188
+ mission_id: The mission ID associated with the job.
189
+ setup_id: The setup ID.
190
+ setup_version_id: The setup version ID associated with the module.
191
+
192
+ Returns:
193
+ str: The unique identifier (job ID) of the created job.
194
+ """
195
+
196
+ @abc.abstractmethod
197
+ async def generate_config_setup_module_response(self, job_id: str) -> SetupModelT | ModuleCodeModel:
198
+ """Generate a stream consumer for a module's output data.
199
+
200
+ This method creates an asynchronous generator that streams output data
201
+ from a specific module job. If the module does not exist, it generates
202
+ an error message.
203
+
204
+ Args:
205
+ job_id: The unique identifier of the job.
206
+
207
+ Returns:
208
+ SetupModelT | ModuleCodeModel: the SetupModelT object fully processed, or an error code.
209
+ """
210
+
211
+ @abc.abstractmethod
212
+ async def create_config_setup_instance_job(
213
+ self,
214
+ config_setup_data: SetupModelT,
215
+ mission_id: str,
216
+ setup_id: str,
217
+ setup_version_id: str,
218
+ ) -> str:
219
+ """Create and start a new module job.
220
+
221
+ This method initializes a new module job, assigns it a unique job ID,
222
+ and starts it in the background.
223
+
224
+ Args:
225
+ config_setup_data: The input data required to start the job.
226
+ mission_id: The mission ID associated with the job.
227
+ setup_id: The setup ID.
228
+ setup_version_id: The setup version ID.
229
+
230
+ Returns:
231
+ str: The unique identifier (job ID) of the created job.
232
+
233
+ Raises:
234
+ Exception: If the module fails to start.
235
+ """
236
+
237
+ @abc.abstractmethod
238
+ async def stop_module(self, job_id: str) -> bool:
239
+ """Stop a running module job.
240
+
241
+ Args:
242
+ job_id: The unique identifier of the job to stop.
243
+
244
+ Returns:
245
+ bool: True if the job was successfully stopped, False if it does not exist.
246
+ """
247
+
248
+ @abc.abstractmethod
249
+ async def get_module_status(self, job_id: str) -> TaskStatus:
250
+ """Retrieve the status of a module job.
251
+
252
+ Args:
253
+ job_id: The unique identifier of the job.
254
+
255
+ Returns:
256
+ ModuleStatu: The status of the job.
257
+ """
258
+
259
+ @abc.abstractmethod
260
+ async def wait_for_completion(self, job_id: str) -> None:
261
+ """Wait for a task to complete.
262
+
263
+ This method blocks until the specified job has reached a terminal state.
264
+ The implementation varies by job manager type:
265
+ - SingleJobManager: Awaits the asyncio.Task directly
266
+ - TaskiqJobManager: Polls task status from SurrealDB
267
+
268
+ Args:
269
+ job_id: The unique identifier of the job to wait for.
270
+
271
+ Raises:
272
+ KeyError: If the job_id is not found.
273
+ """
274
+
275
+ @abc.abstractmethod
276
+ async def stop_all_modules(self) -> None:
277
+ """Stop all currently running module jobs.
278
+
279
+ This method ensures that all active jobs are gracefully terminated.
280
+ """
281
+
282
+ @abc.abstractmethod
283
+ async def list_modules(self) -> dict[str, dict[str, Any]]:
284
+ """List all modules along with their statuses.
285
+
286
+ Returns:
287
+ dict[str, dict[str, Any]]: A dictionary containing information about all modules and their statuses.
288
+ """