digitalkin 0.2.11__py3-none-any.whl → 0.2.13__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.
- digitalkin/__version__.py +1 -1
- digitalkin/grpc_servers/_base_server.py +15 -17
- digitalkin/grpc_servers/module_server.py +9 -10
- digitalkin/grpc_servers/module_servicer.py +108 -85
- digitalkin/grpc_servers/registry_server.py +3 -6
- digitalkin/grpc_servers/registry_servicer.py +18 -19
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +3 -5
- digitalkin/logger.py +45 -1
- digitalkin/models/module/module.py +1 -0
- digitalkin/modules/_base_module.py +47 -6
- digitalkin/modules/job_manager/base_job_manager.py +139 -0
- digitalkin/modules/job_manager/job_manager_models.py +44 -0
- digitalkin/modules/job_manager/single_job_manager.py +218 -0
- digitalkin/modules/job_manager/taskiq_broker.py +173 -0
- digitalkin/modules/job_manager/taskiq_job_manager.py +213 -0
- digitalkin/services/base_strategy.py +3 -1
- digitalkin/services/cost/cost_strategy.py +64 -16
- digitalkin/services/cost/default_cost.py +95 -12
- digitalkin/services/cost/grpc_cost.py +149 -60
- digitalkin/services/filesystem/default_filesystem.py +5 -6
- digitalkin/services/filesystem/filesystem_strategy.py +3 -2
- digitalkin/services/filesystem/grpc_filesystem.py +31 -26
- digitalkin/services/services_config.py +6 -5
- digitalkin/services/setup/__init__.py +1 -0
- digitalkin/services/setup/default_setup.py +10 -12
- digitalkin/services/setup/grpc_setup.py +8 -10
- digitalkin/services/storage/default_storage.py +13 -6
- digitalkin/services/storage/grpc_storage.py +25 -9
- digitalkin/services/storage/storage_strategy.py +3 -2
- digitalkin/utils/arg_parser.py +5 -48
- digitalkin/utils/development_mode_action.py +51 -0
- {digitalkin-0.2.11.dist-info → digitalkin-0.2.13.dist-info}/METADATA +43 -12
- {digitalkin-0.2.11.dist-info → digitalkin-0.2.13.dist-info}/RECORD +40 -33
- {digitalkin-0.2.11.dist-info → digitalkin-0.2.13.dist-info}/WHEEL +1 -1
- modules/cpu_intensive_module.py +271 -0
- modules/minimal_llm_module.py +200 -56
- modules/storage_module.py +5 -6
- modules/text_transform_module.py +1 -1
- digitalkin/modules/job_manager.py +0 -176
- {digitalkin-0.2.11.dist-info → digitalkin-0.2.13.dist-info}/licenses/LICENSE +0 -0
- {digitalkin-0.2.11.dist-info → digitalkin-0.2.13.dist-info}/top_level.txt +0 -0
|
@@ -5,7 +5,6 @@ which handles registration, deregistration, discovery, and status management
|
|
|
5
5
|
of DigitalKin modules.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import logging
|
|
9
8
|
from collections.abc import Iterator
|
|
10
9
|
from enum import Enum
|
|
11
10
|
|
|
@@ -20,7 +19,7 @@ from digitalkin_proto.digitalkin.module_registry.v2 import (
|
|
|
20
19
|
from pydantic import BaseModel
|
|
21
20
|
from typing_extensions import Self
|
|
22
21
|
|
|
23
|
-
logger
|
|
22
|
+
from digitalkin.logger import logger
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
class ExtendedEnum(Enum):
|
|
@@ -184,7 +183,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
184
183
|
registration_pb2.RegisterResponse: A response indicating success or failure.
|
|
185
184
|
"""
|
|
186
185
|
module_id = request.module_id
|
|
187
|
-
logger.
|
|
186
|
+
logger.debug("Registering module: %s", module_id)
|
|
188
187
|
|
|
189
188
|
# Check if module is already registered
|
|
190
189
|
if module_id in self.registered_modules:
|
|
@@ -207,7 +206,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
207
206
|
message=None,
|
|
208
207
|
)
|
|
209
208
|
|
|
210
|
-
logger.
|
|
209
|
+
logger.debug("Module %s registered at %s:%d", module_id, request.address, request.port)
|
|
211
210
|
return registration_pb2.RegisterResponse(success=True)
|
|
212
211
|
|
|
213
212
|
def DeregisterModule( # noqa: N802
|
|
@@ -228,7 +227,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
228
227
|
registration_pb2.DeregisterResponse: A response indicating success or failure.
|
|
229
228
|
"""
|
|
230
229
|
module_id = request.module_id
|
|
231
|
-
logger.
|
|
230
|
+
logger.debug("Deregistering module: %s", module_id)
|
|
232
231
|
|
|
233
232
|
# Check if module exists in registry
|
|
234
233
|
if module_id not in self.registered_modules:
|
|
@@ -242,7 +241,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
242
241
|
# Remove the module
|
|
243
242
|
del self.registered_modules[module_id]
|
|
244
243
|
|
|
245
|
-
logger.
|
|
244
|
+
logger.debug("Module %s deregistered", module_id)
|
|
246
245
|
return registration_pb2.DeregisterResponse(success=True)
|
|
247
246
|
|
|
248
247
|
def DiscoverInfoModule( # noqa: N802
|
|
@@ -261,7 +260,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
261
260
|
Returns:
|
|
262
261
|
discover_pb2.DiscoverInfoResponse: A response containing the module's information.
|
|
263
262
|
"""
|
|
264
|
-
logger.
|
|
263
|
+
logger.debug("Discovering module: %s", request.module_id)
|
|
265
264
|
|
|
266
265
|
# Check if module exists in registry
|
|
267
266
|
if request.module_id not in self.registered_modules:
|
|
@@ -289,24 +288,24 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
289
288
|
Returns:
|
|
290
289
|
discover_pb2.DiscoverSearchResponse: A response containing matching modules.
|
|
291
290
|
"""
|
|
292
|
-
logger.
|
|
291
|
+
logger.debug("Discovering modules with criteria:")
|
|
293
292
|
|
|
294
293
|
# Start with all modules
|
|
295
294
|
results = list(self.registered_modules.values())
|
|
296
|
-
logger.
|
|
295
|
+
logger.debug("%s", list(results))
|
|
297
296
|
# Filter by name if specified
|
|
298
297
|
if request.name:
|
|
299
|
-
logger.
|
|
298
|
+
logger.debug("\tname %s", request.name)
|
|
300
299
|
results = [m for m in results if request.name in m.metadata.name]
|
|
301
300
|
|
|
302
301
|
# Filter by type if specified
|
|
303
302
|
if request.module_type:
|
|
304
|
-
logger.
|
|
303
|
+
logger.debug("\tmodule_type %s", request.module_type)
|
|
305
304
|
results = [m for m in results if m.module_type == request.module_type]
|
|
306
305
|
|
|
307
306
|
# Filter by tags if specified
|
|
308
307
|
if request.tags:
|
|
309
|
-
logger.
|
|
308
|
+
logger.debug("\ttags %s", request.tags)
|
|
310
309
|
results = [m for m in results if any(tag in m.metadata.tags for tag in request.tags)]
|
|
311
310
|
|
|
312
311
|
# Filter by description if specified
|
|
@@ -315,7 +314,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
315
314
|
results = [m for m in results if request.description in m.metadata.description]
|
|
316
315
|
"""
|
|
317
316
|
|
|
318
|
-
logger.
|
|
317
|
+
logger.debug("Found %d matching modules", len(results))
|
|
319
318
|
return discover_pb2.DiscoverSearchResponse(modules=[r.to_proto() for r in results])
|
|
320
319
|
|
|
321
320
|
def GetModuleStatus( # noqa: N802
|
|
@@ -334,7 +333,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
334
333
|
Returns:
|
|
335
334
|
status_pb2.ModuleStatusResponse: A response containing the module's status.
|
|
336
335
|
"""
|
|
337
|
-
logger.
|
|
336
|
+
logger.debug("Getting status for module: %s", request.module_id)
|
|
338
337
|
|
|
339
338
|
# Check if module exists in registry
|
|
340
339
|
if request.module_id not in self.registered_modules:
|
|
@@ -363,7 +362,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
363
362
|
Returns:
|
|
364
363
|
status_pb2.ListModulesStatusResponse: A response containing a list of module statuses.
|
|
365
364
|
"""
|
|
366
|
-
logger.
|
|
365
|
+
logger.debug(
|
|
367
366
|
"Getting registered modules with offset %d and limit %d",
|
|
368
367
|
request.offset,
|
|
369
368
|
request.list_size,
|
|
@@ -384,7 +383,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
384
383
|
for module in list(self.registered_modules.values())[request.offset : request.offset + list_size]
|
|
385
384
|
]
|
|
386
385
|
|
|
387
|
-
logger.
|
|
386
|
+
logger.debug("Found %d registered modules", len(modules_statuses))
|
|
388
387
|
return status_pb2.ListModulesStatusResponse(
|
|
389
388
|
list_size=len(modules_statuses),
|
|
390
389
|
modules_statuses=modules_statuses,
|
|
@@ -406,7 +405,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
406
405
|
Yields:
|
|
407
406
|
status_pb2.ModuleStatusResponse: Responses containing individual module statuses.
|
|
408
407
|
"""
|
|
409
|
-
logger.
|
|
408
|
+
logger.debug("Streaming all %d registered modules", len(self.registered_modules))
|
|
410
409
|
for module in self.registered_modules.values():
|
|
411
410
|
yield status_pb2.ModuleStatusResponse(
|
|
412
411
|
module_id=module.module_id,
|
|
@@ -431,7 +430,7 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
431
430
|
status_pb2.UpdateStatusResponse: A response indicating success or failure.
|
|
432
431
|
"""
|
|
433
432
|
module_id = request.module_id
|
|
434
|
-
logger.
|
|
433
|
+
logger.debug("Updating status for module: %s to %s", module_id, request.status)
|
|
435
434
|
|
|
436
435
|
# Check if module exists in registry
|
|
437
436
|
if request.module_id not in self.registered_modules:
|
|
@@ -453,5 +452,5 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
|
|
|
453
452
|
module_info = self.registered_modules[module_id]
|
|
454
453
|
module_info.status = ModuleStatus(request.status)
|
|
455
454
|
|
|
456
|
-
logger.
|
|
455
|
+
logger.debug("Status for module %s updated to %s", module_id, request.status)
|
|
457
456
|
return status_pb2.UpdateStatusResponse(success=True)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Client wrapper to ease channel creation with specific ServerConfig."""
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from typing import Any
|
|
6
5
|
|
|
@@ -8,8 +7,7 @@ import grpc
|
|
|
8
7
|
|
|
9
8
|
from digitalkin.grpc_servers.utils.exceptions import ServerError
|
|
10
9
|
from digitalkin.grpc_servers.utils.models import ClientConfig, SecurityMode
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
10
|
+
from digitalkin.logger import logger
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class GrpcClientWrapper:
|
|
@@ -64,9 +62,9 @@ class GrpcClientWrapper:
|
|
|
64
62
|
"""
|
|
65
63
|
try:
|
|
66
64
|
# Call the register method
|
|
67
|
-
logger.
|
|
65
|
+
logger.debug("send request to %s", query_endpoint)
|
|
68
66
|
response = getattr(self.stub, query_endpoint)(request)
|
|
69
|
-
logger.
|
|
67
|
+
logger.debug("receive response from request to registry: %s", response)
|
|
70
68
|
except grpc.RpcError:
|
|
71
69
|
logger.exception("RPC error during registration:")
|
|
72
70
|
raise ServerError
|
digitalkin/logger.py
CHANGED
|
@@ -2,11 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import sys
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ColorFormatter(logging.Formatter):
|
|
9
|
+
"""Color formatter for logging."""
|
|
10
|
+
|
|
11
|
+
grey = "\x1b[38;20m"
|
|
12
|
+
green = "\x1b[32;20m"
|
|
13
|
+
blue = "\x1b[34;20m"
|
|
14
|
+
yellow = "\x1b[33;20m"
|
|
15
|
+
red = "\x1b[31;20m"
|
|
16
|
+
bold_red = "\x1b[31;1m"
|
|
17
|
+
reset = "\x1b[0m"
|
|
18
|
+
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" # type: ignore
|
|
19
|
+
|
|
20
|
+
FORMATS: ClassVar[dict[int, str]] = {
|
|
21
|
+
logging.DEBUG: grey + format + reset + "\n", # type: ignore
|
|
22
|
+
logging.INFO: green + format + reset + "\n", # type: ignore
|
|
23
|
+
logging.WARNING: yellow + format + reset + "\n", # type: ignore
|
|
24
|
+
logging.ERROR: red + format + reset + "\n", # type: ignore
|
|
25
|
+
logging.CRITICAL: bold_red + format + reset + "\n", # type: ignore
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def format(self, record: logging.LogRecord) -> str: # type: ignore
|
|
29
|
+
"""Format the log record.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
record: The log record to format.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
str: The formatted log record.
|
|
36
|
+
"""
|
|
37
|
+
log_fmt = self.FORMATS.get(record.levelno)
|
|
38
|
+
formatter = logging.Formatter(log_fmt)
|
|
39
|
+
return formatter.format(record)
|
|
40
|
+
|
|
5
41
|
|
|
6
42
|
logging.basicConfig(
|
|
7
43
|
level=logging.DEBUG,
|
|
8
44
|
stream=sys.stdout,
|
|
9
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
10
45
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
11
46
|
)
|
|
12
47
|
|
|
@@ -15,3 +50,12 @@ logging.getLogger("asyncio").setLevel(logging.DEBUG)
|
|
|
15
50
|
|
|
16
51
|
|
|
17
52
|
logger = logging.getLogger("digitalkin")
|
|
53
|
+
|
|
54
|
+
if not logger.handlers:
|
|
55
|
+
ch = logging.StreamHandler()
|
|
56
|
+
ch.setLevel(logging.INFO)
|
|
57
|
+
|
|
58
|
+
ch.setFormatter(ColorFormatter())
|
|
59
|
+
|
|
60
|
+
logger.addHandler(ch)
|
|
61
|
+
logger.propagate = False
|
|
@@ -7,6 +7,8 @@ from abc import ABC, abstractmethod
|
|
|
7
7
|
from collections.abc import Callable, Coroutine
|
|
8
8
|
from typing import Any, ClassVar, Generic
|
|
9
9
|
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
10
12
|
from digitalkin.logger import logger
|
|
11
13
|
from digitalkin.models.module import InputModelT, ModuleStatus, OutputModelT, SecretModelT, SetupModelT
|
|
12
14
|
from digitalkin.services.agent.agent_strategy import AgentStrategy
|
|
@@ -20,6 +22,14 @@ from digitalkin.services.storage.storage_strategy import StorageStrategy
|
|
|
20
22
|
from digitalkin.utils.llm_ready_schema import llm_ready_schema
|
|
21
23
|
|
|
22
24
|
|
|
25
|
+
class ModuleErrorModel(BaseModel):
|
|
26
|
+
"""Typed error/code model."""
|
|
27
|
+
|
|
28
|
+
code: str
|
|
29
|
+
exception: str
|
|
30
|
+
short_description: str
|
|
31
|
+
|
|
32
|
+
|
|
23
33
|
class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretModelT]):
|
|
24
34
|
"""BaseModule is the abstract base for all modules in the DigitalKin SDK."""
|
|
25
35
|
|
|
@@ -48,17 +58,19 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretMode
|
|
|
48
58
|
def _init_strategies(self) -> None:
|
|
49
59
|
"""Initialize the services configuration."""
|
|
50
60
|
for service_name in self.services_config.valid_strategy_names():
|
|
51
|
-
service = self.services_config.init_strategy(service_name, self.mission_id)
|
|
61
|
+
service = self.services_config.init_strategy(service_name, self.mission_id, self.setup_version_id)
|
|
52
62
|
setattr(self, service_name, service)
|
|
53
63
|
|
|
54
64
|
def __init__(
|
|
55
65
|
self,
|
|
56
66
|
job_id: str,
|
|
57
67
|
mission_id: str,
|
|
68
|
+
setup_version_id: str,
|
|
58
69
|
) -> None:
|
|
59
70
|
"""Initialize the module."""
|
|
60
71
|
self.job_id: str = job_id
|
|
61
72
|
self.mission_id: str = mission_id
|
|
73
|
+
self.setup_version_id: str = setup_version_id
|
|
62
74
|
self._status = ModuleStatus.CREATED
|
|
63
75
|
self._task: asyncio.Task | None = None
|
|
64
76
|
# Initialize services configuration
|
|
@@ -221,30 +233,59 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretMode
|
|
|
221
233
|
asyncio.CancelledError: If the module is cancelled
|
|
222
234
|
"""
|
|
223
235
|
try:
|
|
236
|
+
logger.warning("Starting module %s", self.name)
|
|
224
237
|
await self.run(input_data, setup_data, callback)
|
|
225
|
-
|
|
238
|
+
logger.warning("Module %s finished", self.name)
|
|
226
239
|
except asyncio.CancelledError:
|
|
227
|
-
|
|
240
|
+
self._status = ModuleStatus.CANCELLED
|
|
241
|
+
logger.error(f"Module {self.name} cancelled")
|
|
228
242
|
except Exception:
|
|
229
243
|
self._status = ModuleStatus.FAILED
|
|
230
244
|
logger.exception("Error inside module %s", self.name)
|
|
231
245
|
else:
|
|
232
246
|
self._status = ModuleStatus.STOPPED
|
|
247
|
+
finally:
|
|
248
|
+
await self.stop()
|
|
233
249
|
|
|
234
250
|
async def start(
|
|
235
251
|
self,
|
|
236
252
|
input_data: InputModelT,
|
|
237
253
|
setup_data: SetupModelT,
|
|
238
|
-
callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
|
|
254
|
+
callback: Callable[[OutputModelT | ModuleErrorModel], Coroutine[Any, Any, None]],
|
|
255
|
+
done_callback: Callable | None = None,
|
|
239
256
|
) -> None:
|
|
240
257
|
"""Start the module."""
|
|
241
258
|
try:
|
|
259
|
+
logger.info("Inititalize module")
|
|
242
260
|
await self.initialize(setup_data=setup_data)
|
|
261
|
+
except Exception as e:
|
|
262
|
+
self._status = ModuleStatus.FAILED
|
|
263
|
+
short_description = "Error initializing module"
|
|
264
|
+
logger.exception("%s: %s", short_description, e)
|
|
265
|
+
await callback(
|
|
266
|
+
ModuleErrorModel(
|
|
267
|
+
code=str(self._status),
|
|
268
|
+
short_description=short_description,
|
|
269
|
+
exception=str(e),
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
if done_callback is not None:
|
|
273
|
+
await done_callback(None)
|
|
274
|
+
await self.stop()
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
logger.info("Run lifecycle")
|
|
243
279
|
self._status = ModuleStatus.RUNNING
|
|
244
|
-
self._task = asyncio.create_task(
|
|
280
|
+
self._task = asyncio.create_task(
|
|
281
|
+
self._run_lifecycle(input_data, setup_data, callback),
|
|
282
|
+
name="module_lifecycle",
|
|
283
|
+
)
|
|
284
|
+
if done_callback is not None:
|
|
285
|
+
self._task.add_done_callback(done_callback)
|
|
245
286
|
except Exception:
|
|
246
287
|
self._status = ModuleStatus.FAILED
|
|
247
|
-
logger.exception("Error
|
|
288
|
+
logger.exception("Error during module lifecyle")
|
|
248
289
|
|
|
249
290
|
async def stop(self) -> None:
|
|
250
291
|
"""Stop the module."""
|
|
@@ -0,0 +1,139 @@
|
|
|
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.models import ModuleStatus
|
|
9
|
+
from digitalkin.models.module import InputModelT, OutputModelT, SetupModelT
|
|
10
|
+
from digitalkin.modules._base_module import BaseModule
|
|
11
|
+
from digitalkin.services.services_config import ServicesConfig
|
|
12
|
+
from digitalkin.services.services_models import ServicesMode
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
|
|
16
|
+
"""Abstract base class for managing background module jobs."""
|
|
17
|
+
|
|
18
|
+
async def _start(self) -> None:
|
|
19
|
+
"""Start the job manager.
|
|
20
|
+
|
|
21
|
+
This method initializes any necessary resources or configurations
|
|
22
|
+
required for the job manager to function.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
async def job_specific_callback(
|
|
27
|
+
callback: Callable[[str, OutputModelT], Coroutine[Any, Any, None]], job_id: str
|
|
28
|
+
) -> Callable[[OutputModelT], Coroutine[Any, Any, None]]:
|
|
29
|
+
"""Generate a job-specific callback function.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
callback: The callback function to be executed when the job completes.
|
|
33
|
+
job_id: The unique identifier of the job.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Callable: A wrapped callback function that includes the job ID.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def callback_wrapper(output_data: OutputModelT) -> Coroutine[Any, Any, None]:
|
|
40
|
+
"""Wrapper for the callback function.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
output_data: The output data produced by the job.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Coroutine: The wrapped callback function.
|
|
47
|
+
"""
|
|
48
|
+
return callback(job_id, output_data)
|
|
49
|
+
|
|
50
|
+
return callback_wrapper
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
module_class: type[BaseModule],
|
|
55
|
+
services_mode: ServicesMode,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Initialize the job manager.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
module_class: The class of the module to be managed.
|
|
61
|
+
services_mode: The mode of operation for the services (e.g., ASYNC or SYNC).
|
|
62
|
+
"""
|
|
63
|
+
self.module_class = module_class
|
|
64
|
+
|
|
65
|
+
services_config = ServicesConfig(
|
|
66
|
+
services_config_strategies=self.module_class.services_config_strategies,
|
|
67
|
+
services_config_params=self.module_class.services_config_params,
|
|
68
|
+
mode=services_mode,
|
|
69
|
+
)
|
|
70
|
+
setattr(self.module_class, "services_config", services_config)
|
|
71
|
+
|
|
72
|
+
@abc.abstractmethod # type: ignore
|
|
73
|
+
@asynccontextmanager # type: ignore
|
|
74
|
+
async def generate_stream_consumer(self, job_id: str) -> AsyncIterator[AsyncGenerator[dict[str, Any], None]]:
|
|
75
|
+
"""Generate a stream consumer for the job's message stream.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
job_id: The unique identifier of the job to filter messages for.
|
|
79
|
+
|
|
80
|
+
Yields:
|
|
81
|
+
dict[str, Any]: The messages from the associated module's stream.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
@abc.abstractmethod
|
|
85
|
+
async def create_job(
|
|
86
|
+
self,
|
|
87
|
+
input_data: InputModelT,
|
|
88
|
+
setup_data: SetupModelT,
|
|
89
|
+
mission_id: str,
|
|
90
|
+
setup_version_id: str,
|
|
91
|
+
) -> str:
|
|
92
|
+
"""Create and start a new job for the module.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
input_data: The input data required to start the job.
|
|
96
|
+
setup_data: The setup configuration for the module.
|
|
97
|
+
mission_id: The mission ID associated with the job.
|
|
98
|
+
setup_version_id: The setup ID associated with the module.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
str: The unique identifier (job ID) of the created job.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
@abc.abstractmethod
|
|
105
|
+
async def stop_module(self, job_id: str) -> bool:
|
|
106
|
+
"""Stop a running module job.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
job_id: The unique identifier of the job to stop.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
bool: True if the job was successfully stopped, False if it does not exist.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
@abc.abstractmethod
|
|
116
|
+
async def get_module_status(self, job_id: str) -> ModuleStatus | None:
|
|
117
|
+
"""Retrieve the status of a module job.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
job_id: The unique identifier of the job.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
ModuleStatus | None: The status of the job, or None if the job does not exist.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
@abc.abstractmethod
|
|
127
|
+
async def stop_all_modules(self) -> None:
|
|
128
|
+
"""Stop all currently running module jobs.
|
|
129
|
+
|
|
130
|
+
This method ensures that all active jobs are gracefully terminated.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
@abc.abstractmethod
|
|
134
|
+
async def list_modules(self) -> dict[str, dict[str, Any]]:
|
|
135
|
+
"""List all modules along with their statuses.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
dict[str, dict[str, Any]]: A dictionary containing information about all modules and their statuses.
|
|
139
|
+
"""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Job manager models."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from digitalkin.modules.job_manager.base_job_manager import BaseJobManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StreamCodeModel(BaseModel):
|
|
11
|
+
"""Typed error/code model."""
|
|
12
|
+
|
|
13
|
+
code: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class JobManagerMode(Enum):
|
|
17
|
+
"""Job manager mode."""
|
|
18
|
+
|
|
19
|
+
SINGLE = "single"
|
|
20
|
+
TASKIQ = "taskiq"
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
"""Get the string representation of the job manager mode.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
str: job manager mode name.
|
|
27
|
+
"""
|
|
28
|
+
return self.value
|
|
29
|
+
|
|
30
|
+
def get_manager_class(self) -> type[BaseJobManager]:
|
|
31
|
+
"""Get the job manager class based on the mode.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
type: The job manager class.
|
|
35
|
+
"""
|
|
36
|
+
match self:
|
|
37
|
+
case JobManagerMode.SINGLE:
|
|
38
|
+
from digitalkin.modules.job_manager.single_job_manager import SingleJobManager # noqa: PLC0415
|
|
39
|
+
|
|
40
|
+
return SingleJobManager
|
|
41
|
+
case JobManagerMode.TASKIQ:
|
|
42
|
+
from digitalkin.modules.job_manager.taskiq_job_manager import TaskiqJobManager # noqa: PLC0415
|
|
43
|
+
|
|
44
|
+
return TaskiqJobManager
|