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.
- base_server/server_async_insecure.py +6 -5
- base_server/server_async_secure.py +6 -5
- base_server/server_sync_insecure.py +5 -4
- base_server/server_sync_secure.py +5 -4
- digitalkin/__version__.py +1 -1
- 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/{modules → core}/job_manager/base_job_manager.py +138 -32
- digitalkin/core/job_manager/single_job_manager.py +373 -0
- digitalkin/{modules → core}/job_manager/taskiq_broker.py +121 -26
- 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 +368 -0
- digitalkin/grpc_servers/__init__.py +1 -19
- digitalkin/grpc_servers/_base_server.py +3 -3
- digitalkin/grpc_servers/module_server.py +120 -195
- digitalkin/grpc_servers/module_servicer.py +81 -44
- digitalkin/grpc_servers/utils/__init__.py +1 -0
- digitalkin/grpc_servers/utils/exceptions.py +0 -8
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +25 -9
- digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
- digitalkin/grpc_servers/utils/utility_schema_extender.py +100 -0
- digitalkin/logger.py +64 -27
- 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 +1 -1
- digitalkin/models/core/__init__.py +1 -0
- digitalkin/{modules/job_manager → models/core}/job_manager_models.py +3 -11
- digitalkin/models/core/task_monitor.py +74 -0
- digitalkin/models/grpc_servers/__init__.py +1 -0
- digitalkin/{grpc_servers/utils → models/grpc_servers}/models.py +92 -7
- digitalkin/models/module/__init__.py +18 -11
- digitalkin/models/module/base_types.py +61 -0
- digitalkin/models/module/module.py +9 -1
- digitalkin/models/module/module_context.py +282 -6
- digitalkin/models/module/module_types.py +29 -105
- digitalkin/models/module/setup_types.py +490 -0
- digitalkin/models/module/tool_cache.py +68 -0
- digitalkin/models/module/tool_reference.py +117 -0
- digitalkin/models/module/utility.py +167 -0
- digitalkin/models/services/__init__.py +9 -0
- digitalkin/models/services/cost.py +1 -0
- digitalkin/models/services/registry.py +35 -0
- digitalkin/models/services/storage.py +39 -5
- digitalkin/modules/__init__.py +5 -1
- digitalkin/modules/_base_module.py +265 -167
- digitalkin/modules/archetype_module.py +6 -1
- digitalkin/modules/tool_module.py +16 -3
- digitalkin/modules/trigger_handler.py +7 -6
- digitalkin/modules/triggers/__init__.py +8 -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/services/__init__.py +4 -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 +234 -0
- digitalkin/services/cost/__init__.py +9 -2
- digitalkin/services/cost/grpc_cost.py +9 -42
- digitalkin/services/filesystem/default_filesystem.py +0 -2
- digitalkin/services/filesystem/grpc_filesystem.py +10 -39
- digitalkin/services/registry/__init__.py +22 -1
- digitalkin/services/registry/default_registry.py +135 -4
- digitalkin/services/registry/exceptions.py +47 -0
- digitalkin/services/registry/grpc_registry.py +306 -0
- digitalkin/services/registry/registry_models.py +15 -0
- digitalkin/services/registry/registry_strategy.py +88 -4
- digitalkin/services/services_config.py +25 -3
- digitalkin/services/services_models.py +5 -1
- digitalkin/services/setup/default_setup.py +6 -7
- digitalkin/services/setup/grpc_setup.py +52 -15
- digitalkin/services/storage/grpc_storage.py +4 -4
- 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 +25 -0
- digitalkin/utils/__init__.py +28 -0
- digitalkin/utils/arg_parser.py +1 -1
- digitalkin/utils/development_mode_action.py +2 -2
- digitalkin/utils/dynamic_schema.py +483 -0
- digitalkin/utils/package_discover.py +1 -2
- digitalkin/utils/schema_splitter.py +207 -0
- {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/METADATA +11 -30
- digitalkin-0.3.2.dev14.dist-info/RECORD +143 -0
- {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/top_level.txt +1 -0
- modules/archetype_with_tools_module.py +244 -0
- modules/cpu_intensive_module.py +1 -1
- modules/dynamic_setup_module.py +338 -0
- modules/minimal_llm_module.py +1 -1
- modules/text_transform_module.py +1 -1
- monitoring/digitalkin_observability/__init__.py +46 -0
- monitoring/digitalkin_observability/http_server.py +150 -0
- monitoring/digitalkin_observability/interceptors.py +176 -0
- monitoring/digitalkin_observability/metrics.py +201 -0
- monitoring/digitalkin_observability/prometheus.py +137 -0
- monitoring/tests/test_metrics.py +172 -0
- services/filesystem_module.py +7 -5
- services/storage_module.py +4 -2
- digitalkin/grpc_servers/registry_server.py +0 -65
- digitalkin/grpc_servers/registry_servicer.py +0 -456
- digitalkin/grpc_servers/utils/factory.py +0 -180
- digitalkin/modules/job_manager/single_job_manager.py +0 -294
- digitalkin/modules/job_manager/taskiq_job_manager.py +0 -290
- digitalkin-0.2.25rc0.dist-info/RECORD +0 -89
- /digitalkin/{grpc_servers/utils → models/grpc_servers}/types.py +0 -0
- {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/WHEEL +0 -0
- {digitalkin-0.2.25rc0.dist-info → digitalkin-0.3.2.dev14.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,8 +9,9 @@ from pathlib import Path
|
|
|
9
9
|
# Add parent directory to path to enable imports
|
|
10
10
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
|
11
11
|
|
|
12
|
-
from digitalkin.grpc_servers._base_server import BaseServer
|
|
13
12
|
from digitalkin.grpc_servers.utils.models import SecurityMode, ServerConfig, ServerMode
|
|
13
|
+
|
|
14
|
+
from digitalkin.grpc_servers._base_server import BaseServer
|
|
14
15
|
from examples.base_server.mock.mock_pb2 import DESCRIPTOR, HelloReply # type: ignore
|
|
15
16
|
from examples.base_server.mock.mock_pb2_grpc import (
|
|
16
17
|
Greeter,
|
|
@@ -30,7 +31,7 @@ class AsyncGreeterImpl(Greeter):
|
|
|
30
31
|
|
|
31
32
|
async def SayHello(self, request, context): # noqa: N802
|
|
32
33
|
"""Asynchronous implementation of SayHello method."""
|
|
33
|
-
logger.info(
|
|
34
|
+
logger.info("Received request object: %s", request)
|
|
34
35
|
logger.info(f"Request attributes: {vars(request)}")
|
|
35
36
|
logger.info(f"Received request with name: {request.name}")
|
|
36
37
|
|
|
@@ -40,7 +41,7 @@ class AsyncGreeterImpl(Greeter):
|
|
|
40
41
|
name = "unknown"
|
|
41
42
|
# Check context metadata
|
|
42
43
|
for key, value in context.invocation_metadata():
|
|
43
|
-
logger.info(
|
|
44
|
+
logger.info("Metadata: %s=%s", key, value)
|
|
44
45
|
if key.lower() == "name":
|
|
45
46
|
name = value
|
|
46
47
|
|
|
@@ -97,7 +98,7 @@ async def main_async() -> int:
|
|
|
97
98
|
# as the KeyboardInterrupt usually breaks out of asyncio.run()
|
|
98
99
|
logger.info("Server stopping due to keyboard interrupt...")
|
|
99
100
|
except Exception as e:
|
|
100
|
-
logger.exception(
|
|
101
|
+
logger.exception("Error running server: %s", e)
|
|
101
102
|
return 1
|
|
102
103
|
finally:
|
|
103
104
|
# Clean up resources if server was started
|
|
@@ -116,7 +117,7 @@ def main():
|
|
|
116
117
|
logger.info("Server stopped by keyboard interrupt")
|
|
117
118
|
return 0 # Clean exit
|
|
118
119
|
except Exception as e:
|
|
119
|
-
logger.exception(
|
|
120
|
+
logger.exception("Fatal error: %s", e)
|
|
120
121
|
return 1
|
|
121
122
|
|
|
122
123
|
|
|
@@ -9,13 +9,14 @@ from pathlib import Path
|
|
|
9
9
|
# Add parent directory to path to enable imports
|
|
10
10
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
|
11
11
|
|
|
12
|
-
from digitalkin.grpc_servers._base_server import BaseServer
|
|
13
12
|
from digitalkin.grpc_servers.utils.models import (
|
|
14
13
|
SecurityMode,
|
|
15
14
|
ServerConfig,
|
|
16
15
|
ServerCredentials,
|
|
17
16
|
ServerMode,
|
|
18
17
|
)
|
|
18
|
+
|
|
19
|
+
from digitalkin.grpc_servers._base_server import BaseServer
|
|
19
20
|
from examples.base_server.mock.mock_pb2 import DESCRIPTOR, HelloReply # type: ignore
|
|
20
21
|
from examples.base_server.mock.mock_pb2_grpc import (
|
|
21
22
|
Greeter,
|
|
@@ -35,7 +36,7 @@ class AsyncGreeterImpl(Greeter):
|
|
|
35
36
|
|
|
36
37
|
async def SayHello(self, request, context): # noqa: N802
|
|
37
38
|
"""Asynchronous implementation of SayHello method."""
|
|
38
|
-
logger.info(
|
|
39
|
+
logger.info("Received request object: %s", request)
|
|
39
40
|
logger.info(f"Request attributes: {vars(request)}")
|
|
40
41
|
logger.info(f"Received request with name: {request.name}")
|
|
41
42
|
|
|
@@ -45,7 +46,7 @@ class AsyncGreeterImpl(Greeter):
|
|
|
45
46
|
name = "unknown"
|
|
46
47
|
# Check context metadata
|
|
47
48
|
for key, value in context.invocation_metadata():
|
|
48
|
-
logger.info(
|
|
49
|
+
logger.info("Metadata: %s=%s", key, value)
|
|
49
50
|
if key.lower() == "name":
|
|
50
51
|
name = value
|
|
51
52
|
|
|
@@ -115,7 +116,7 @@ async def main_async() -> int:
|
|
|
115
116
|
# as the KeyboardInterrupt usually breaks out of asyncio.run()
|
|
116
117
|
logger.info("Server stopping due to keyboard interrupt...")
|
|
117
118
|
except Exception as e:
|
|
118
|
-
logger.exception(
|
|
119
|
+
logger.exception("Error running server: %s", e)
|
|
119
120
|
return 1
|
|
120
121
|
finally:
|
|
121
122
|
# Clean up resources if server was started
|
|
@@ -134,7 +135,7 @@ def main():
|
|
|
134
135
|
logger.info("Server stopped by keyboard interrupt")
|
|
135
136
|
return 0 # Clean exit
|
|
136
137
|
except Exception as e:
|
|
137
|
-
logger.exception(
|
|
138
|
+
logger.exception("Fatal error: %s", e)
|
|
138
139
|
return 1
|
|
139
140
|
|
|
140
141
|
|
|
@@ -8,8 +8,9 @@ from pathlib import Path
|
|
|
8
8
|
# Add parent directory to path to enable imports
|
|
9
9
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
|
10
10
|
|
|
11
|
-
from digitalkin.grpc_servers._base_server import BaseServer
|
|
12
11
|
from digitalkin.grpc_servers.utils.models import SecurityMode, ServerConfig, ServerMode
|
|
12
|
+
|
|
13
|
+
from digitalkin.grpc_servers._base_server import BaseServer
|
|
13
14
|
from examples.base_server.mock.mock_pb2 import DESCRIPTOR, HelloReply # type: ignore
|
|
14
15
|
from examples.base_server.mock.mock_pb2_grpc import (
|
|
15
16
|
Greeter,
|
|
@@ -29,7 +30,7 @@ class SyncGreeterServicer(Greeter):
|
|
|
29
30
|
|
|
30
31
|
def SayHello(self, request, context): # noqa: N802
|
|
31
32
|
"""Implementation of SayHello method."""
|
|
32
|
-
logger.info(
|
|
33
|
+
logger.info("Received request object: %s", request)
|
|
33
34
|
logger.info(f"Request attributes: {vars(request)}")
|
|
34
35
|
logger.info(f"Received request with name: {request.name}")
|
|
35
36
|
|
|
@@ -39,7 +40,7 @@ class SyncGreeterServicer(Greeter):
|
|
|
39
40
|
name = "unknown"
|
|
40
41
|
# Check context metadata
|
|
41
42
|
for key, value in context.invocation_metadata():
|
|
42
|
-
logger.info(
|
|
43
|
+
logger.info("Metadata: %s=%s", key, value)
|
|
43
44
|
if key.lower() == "name":
|
|
44
45
|
name = value
|
|
45
46
|
|
|
@@ -92,7 +93,7 @@ def main() -> int:
|
|
|
92
93
|
server.stop()
|
|
93
94
|
|
|
94
95
|
except Exception as e:
|
|
95
|
-
logger.exception(
|
|
96
|
+
logger.exception("Error running server: %s", e)
|
|
96
97
|
return 1
|
|
97
98
|
|
|
98
99
|
return 0
|
|
@@ -8,13 +8,14 @@ from pathlib import Path
|
|
|
8
8
|
# Add parent directory to path to enable imports
|
|
9
9
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
|
10
10
|
|
|
11
|
-
from digitalkin.grpc_servers._base_server import BaseServer
|
|
12
11
|
from digitalkin.grpc_servers.utils.models import (
|
|
13
12
|
SecurityMode,
|
|
14
13
|
ServerConfig,
|
|
15
14
|
ServerCredentials,
|
|
16
15
|
ServerMode,
|
|
17
16
|
)
|
|
17
|
+
|
|
18
|
+
from digitalkin.grpc_servers._base_server import BaseServer
|
|
18
19
|
from examples.base_server.mock.mock_pb2 import DESCRIPTOR, HelloReply # type: ignore
|
|
19
20
|
from examples.base_server.mock.mock_pb2_grpc import (
|
|
20
21
|
Greeter,
|
|
@@ -34,7 +35,7 @@ class SyncGreeterServicer(Greeter):
|
|
|
34
35
|
|
|
35
36
|
def SayHello(self, request, context): # noqa: N802
|
|
36
37
|
"""Implementation of SayHello method."""
|
|
37
|
-
logger.info(
|
|
38
|
+
logger.info("Received request object: %s", request)
|
|
38
39
|
logger.info(f"Request attributes: {vars(request)}")
|
|
39
40
|
logger.info(f"Received request with name: {request.name}")
|
|
40
41
|
|
|
@@ -44,7 +45,7 @@ class SyncGreeterServicer(Greeter):
|
|
|
44
45
|
name = "unknown"
|
|
45
46
|
# Check context metadata
|
|
46
47
|
for key, value in context.invocation_metadata():
|
|
47
|
-
logger.info(
|
|
48
|
+
logger.info("Metadata: %s=%s", key, value)
|
|
48
49
|
if key.lower() == "name":
|
|
49
50
|
name = value
|
|
50
51
|
|
|
@@ -111,7 +112,7 @@ def main() -> int:
|
|
|
111
112
|
server.stop()
|
|
112
113
|
|
|
113
114
|
except Exception as e:
|
|
114
|
-
logger.exception(
|
|
115
|
+
logger.exception("Error running server: %s", e)
|
|
115
116
|
return 1
|
|
116
117
|
|
|
117
118
|
return 0
|
digitalkin/__version__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core of Digitlakin defining the task management and sub-modules."""
|
|
@@ -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."""
|
|
@@ -5,17 +5,126 @@ from collections.abc import AsyncGenerator, AsyncIterator, Callable, Coroutine
|
|
|
5
5
|
from contextlib import asynccontextmanager
|
|
6
6
|
from typing import Any, Generic
|
|
7
7
|
|
|
8
|
-
from digitalkin.
|
|
9
|
-
from digitalkin.
|
|
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
|
|
10
13
|
from digitalkin.modules._base_module import BaseModule
|
|
11
14
|
from digitalkin.services.services_config import ServicesConfig
|
|
12
15
|
from digitalkin.services.services_models import ServicesMode
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
|
|
16
|
-
"""Abstract base class for managing background module jobs.
|
|
18
|
+
class BaseJobManager(abc.ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
|
|
19
|
+
"""Abstract base class for managing background module jobs.
|
|
17
20
|
|
|
18
|
-
|
|
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:
|
|
19
128
|
"""Start the job manager.
|
|
20
129
|
|
|
21
130
|
This method initializes any necessary resources or configurations
|
|
@@ -24,8 +133,9 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
|
|
|
24
133
|
|
|
25
134
|
@staticmethod
|
|
26
135
|
async def job_specific_callback(
|
|
27
|
-
callback: Callable[[str, OutputModelT], Coroutine[Any, Any, None]],
|
|
28
|
-
|
|
136
|
+
callback: Callable[[str, OutputModelT | ModuleCodeModel], Coroutine[Any, Any, None]],
|
|
137
|
+
job_id: str,
|
|
138
|
+
) -> Callable[[OutputModelT | ModuleCodeModel], Coroutine[Any, Any, None]]:
|
|
29
139
|
"""Generate a job-specific callback function.
|
|
30
140
|
|
|
31
141
|
Args:
|
|
@@ -36,7 +146,7 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
|
|
|
36
146
|
Callable: A wrapped callback function that includes the job ID.
|
|
37
147
|
"""
|
|
38
148
|
|
|
39
|
-
def callback_wrapper(output_data: OutputModelT) -> Coroutine[Any, Any, None]:
|
|
149
|
+
def callback_wrapper(output_data: OutputModelT | ModuleCodeModel) -> Coroutine[Any, Any, None]:
|
|
40
150
|
"""Wrapper for the callback function.
|
|
41
151
|
|
|
42
152
|
Args:
|
|
@@ -49,26 +159,6 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
|
|
|
49
159
|
|
|
50
160
|
return callback_wrapper
|
|
51
161
|
|
|
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
162
|
@abc.abstractmethod # type: ignore
|
|
73
163
|
@asynccontextmanager # type: ignore
|
|
74
164
|
async def generate_stream_consumer(self, job_id: str) -> AsyncIterator[AsyncGenerator[dict[str, Any], None]]:
|
|
@@ -104,7 +194,7 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
|
|
|
104
194
|
"""
|
|
105
195
|
|
|
106
196
|
@abc.abstractmethod
|
|
107
|
-
async def generate_config_setup_module_response(self, job_id: str) -> SetupModelT:
|
|
197
|
+
async def generate_config_setup_module_response(self, job_id: str) -> SetupModelT | ModuleCodeModel:
|
|
108
198
|
"""Generate a stream consumer for a module's output data.
|
|
109
199
|
|
|
110
200
|
This method creates an asynchronous generator that streams output data
|
|
@@ -115,7 +205,7 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
|
|
|
115
205
|
job_id: The unique identifier of the job.
|
|
116
206
|
|
|
117
207
|
Returns:
|
|
118
|
-
SetupModelT: the SetupModelT object fully processed.
|
|
208
|
+
SetupModelT | ModuleCodeModel: the SetupModelT object fully processed, or an error code.
|
|
119
209
|
"""
|
|
120
210
|
|
|
121
211
|
@abc.abstractmethod
|
|
@@ -156,14 +246,30 @@ class BaseJobManager(abc.ABC, Generic[InputModelT, SetupModelT]):
|
|
|
156
246
|
"""
|
|
157
247
|
|
|
158
248
|
@abc.abstractmethod
|
|
159
|
-
async def get_module_status(self, job_id: str) ->
|
|
249
|
+
async def get_module_status(self, job_id: str) -> TaskStatus:
|
|
160
250
|
"""Retrieve the status of a module job.
|
|
161
251
|
|
|
162
252
|
Args:
|
|
163
253
|
job_id: The unique identifier of the job.
|
|
164
254
|
|
|
165
255
|
Returns:
|
|
166
|
-
|
|
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.
|
|
167
273
|
"""
|
|
168
274
|
|
|
169
275
|
@abc.abstractmethod
|