digitalkin 0.2.12__py3-none-any.whl → 0.2.14__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 +199 -85
- digitalkin/grpc_servers/registry_server.py +3 -6
- digitalkin/grpc_servers/registry_servicer.py +18 -19
- digitalkin/grpc_servers/utils/exceptions.py +4 -0
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +3 -5
- digitalkin/logger.py +45 -1
- digitalkin/models/module/__init__.py +2 -1
- digitalkin/models/module/module.py +1 -0
- digitalkin/models/module/module_types.py +1 -0
- digitalkin/modules/_base_module.py +124 -7
- digitalkin/modules/archetype_module.py +11 -1
- digitalkin/modules/job_manager/base_job_manager.py +181 -0
- digitalkin/modules/job_manager/job_manager_models.py +44 -0
- digitalkin/modules/job_manager/single_job_manager.py +285 -0
- digitalkin/modules/job_manager/taskiq_broker.py +214 -0
- digitalkin/modules/job_manager/taskiq_job_manager.py +286 -0
- digitalkin/modules/tool_module.py +2 -1
- digitalkin/modules/trigger_module.py +3 -1
- digitalkin/services/cost/default_cost.py +8 -4
- digitalkin/services/cost/grpc_cost.py +15 -7
- digitalkin/services/filesystem/default_filesystem.py +2 -4
- digitalkin/services/filesystem/grpc_filesystem.py +8 -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 +11 -5
- digitalkin/services/storage/grpc_storage.py +23 -8
- digitalkin/utils/arg_parser.py +5 -48
- digitalkin/utils/development_mode_action.py +51 -0
- {digitalkin-0.2.12.dist-info → digitalkin-0.2.14.dist-info}/METADATA +46 -15
- {digitalkin-0.2.12.dist-info → digitalkin-0.2.14.dist-info}/RECORD +41 -34
- {digitalkin-0.2.12.dist-info → digitalkin-0.2.14.dist-info}/WHEEL +1 -1
- modules/cpu_intensive_module.py +281 -0
- modules/minimal_llm_module.py +240 -58
- modules/storage_module.py +5 -6
- modules/text_transform_module.py +1 -1
- digitalkin/modules/job_manager.py +0 -177
- {digitalkin-0.2.12.dist-info → digitalkin-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {digitalkin-0.2.12.dist-info → digitalkin-0.2.14.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Registry gRPC server implementation for DigitalKin."""
|
|
2
2
|
|
|
3
|
-
import logging
|
|
4
|
-
|
|
5
3
|
from digitalkin_proto.digitalkin.module_registry.v2 import (
|
|
6
4
|
module_registry_service_pb2,
|
|
7
5
|
module_registry_service_pb2_grpc,
|
|
@@ -10,8 +8,7 @@ from digitalkin_proto.digitalkin.module_registry.v2 import (
|
|
|
10
8
|
from digitalkin.grpc_servers._base_server import BaseServer
|
|
11
9
|
from digitalkin.grpc_servers.registry_servicer import RegistryModule, RegistryServicer
|
|
12
10
|
from digitalkin.grpc_servers.utils.models import RegistryServerConfig
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
11
|
+
from digitalkin.logger import logger
|
|
15
12
|
|
|
16
13
|
|
|
17
14
|
class RegistryServer(BaseServer):
|
|
@@ -48,14 +45,14 @@ class RegistryServer(BaseServer):
|
|
|
48
45
|
msg = "Server must be created before registering servicers"
|
|
49
46
|
raise RuntimeError(msg)
|
|
50
47
|
|
|
51
|
-
logger.
|
|
48
|
+
logger.debug("Registering registry servicer")
|
|
52
49
|
self.registry_servicer = RegistryServicer()
|
|
53
50
|
self.register_servicer(
|
|
54
51
|
self.registry_servicer,
|
|
55
52
|
module_registry_service_pb2_grpc.add_ModuleRegistryServiceServicer_to_server,
|
|
56
53
|
service_descriptor=module_registry_service_pb2.DESCRIPTOR,
|
|
57
54
|
)
|
|
58
|
-
logger.
|
|
55
|
+
logger.debug("Registered registry servicer")
|
|
59
56
|
|
|
60
57
|
def get_registered_modules(self) -> list[RegistryModule]:
|
|
61
58
|
"""Get a list of all registered modules.
|
|
@@ -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)
|
|
@@ -31,3 +31,7 @@ class ReflectionError(ServerError):
|
|
|
31
31
|
|
|
32
32
|
class HealthCheckError(ServerError):
|
|
33
33
|
"""Error related to gRPC health check service."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OptionalFeatureNotImplementedError(NotImplementedError):
|
|
37
|
+
"""Raised when an optional feature is not implemented, but was requested."""
|
|
@@ -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
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from digitalkin.models.module.module import Module, ModuleStatus
|
|
4
4
|
from digitalkin.models.module.module_types import (
|
|
5
|
+
ConfigSetupModelT,
|
|
5
6
|
InputModelT,
|
|
6
7
|
OutputModelT,
|
|
7
8
|
SecretModelT,
|
|
8
9
|
SetupModelT,
|
|
9
10
|
)
|
|
10
11
|
|
|
11
|
-
__all__ = ["InputModelT", "Module", "ModuleStatus", "OutputModelT", "SecretModelT", "SetupModelT"]
|
|
12
|
+
__all__ = ["ConfigSetupModelT", "InputModelT", "Module", "ModuleStatus", "OutputModelT", "SecretModelT", "SetupModelT"]
|
|
@@ -4,6 +4,7 @@ from typing import TypeVar
|
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
|
|
7
|
+
ConfigSetupModelT = TypeVar("ConfigSetupModelT", bound=BaseModel | None)
|
|
7
8
|
InputModelT = TypeVar("InputModelT", bound=BaseModel)
|
|
8
9
|
OutputModelT = TypeVar("OutputModelT", bound=BaseModel)
|
|
9
10
|
SetupModelT = TypeVar("SetupModelT", bound=BaseModel)
|
|
@@ -7,8 +7,18 @@ 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
|
+
|
|
12
|
+
from digitalkin.grpc_servers.utils.exceptions import OptionalFeatureNotImplementedError
|
|
10
13
|
from digitalkin.logger import logger
|
|
11
|
-
from digitalkin.models.module import
|
|
14
|
+
from digitalkin.models.module import (
|
|
15
|
+
ConfigSetupModelT,
|
|
16
|
+
InputModelT,
|
|
17
|
+
ModuleStatus,
|
|
18
|
+
OutputModelT,
|
|
19
|
+
SecretModelT,
|
|
20
|
+
SetupModelT,
|
|
21
|
+
)
|
|
12
22
|
from digitalkin.services.agent.agent_strategy import AgentStrategy
|
|
13
23
|
from digitalkin.services.cost.cost_strategy import CostStrategy
|
|
14
24
|
from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
|
|
@@ -20,11 +30,30 @@ from digitalkin.services.storage.storage_strategy import StorageStrategy
|
|
|
20
30
|
from digitalkin.utils.llm_ready_schema import llm_ready_schema
|
|
21
31
|
|
|
22
32
|
|
|
23
|
-
class
|
|
33
|
+
class ModuleErrorModel(BaseModel):
|
|
34
|
+
"""Typed error/code model."""
|
|
35
|
+
|
|
36
|
+
code: str
|
|
37
|
+
exception: str
|
|
38
|
+
short_description: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BaseModule(
|
|
42
|
+
ABC,
|
|
43
|
+
Generic[
|
|
44
|
+
InputModelT,
|
|
45
|
+
OutputModelT,
|
|
46
|
+
SetupModelT,
|
|
47
|
+
SecretModelT,
|
|
48
|
+
ConfigSetupModelT,
|
|
49
|
+
],
|
|
50
|
+
):
|
|
24
51
|
"""BaseModule is the abstract base for all modules in the DigitalKin SDK."""
|
|
25
52
|
|
|
26
53
|
name: str
|
|
27
54
|
description: str
|
|
55
|
+
|
|
56
|
+
config_setup_format: type[ConfigSetupModelT]
|
|
28
57
|
input_format: type[InputModelT]
|
|
29
58
|
output_format: type[OutputModelT]
|
|
30
59
|
setup_format: type[SetupModelT]
|
|
@@ -126,6 +155,23 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretMode
|
|
|
126
155
|
msg = "'%s' class does not define an 'output_format'."
|
|
127
156
|
raise NotImplementedError(msg)
|
|
128
157
|
|
|
158
|
+
@classmethod
|
|
159
|
+
def get_config_setup_format(cls, *, llm_format: bool) -> str:
|
|
160
|
+
"""Gets the JSON schema of the config setup format model.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
OptionalFeatureNotImplementedError: If the `config_setup_format` is not defined.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
The JSON schema of the config setup format as a string.
|
|
167
|
+
"""
|
|
168
|
+
if cls.config_setup_format is not None:
|
|
169
|
+
if llm_format:
|
|
170
|
+
return json.dumps(llm_ready_schema(cls.config_setup_format), indent=2)
|
|
171
|
+
return json.dumps(cls.config_setup_format.model_json_schema(), indent=2)
|
|
172
|
+
msg = "'%s' class does not define an 'config_setup_format'."
|
|
173
|
+
raise OptionalFeatureNotImplementedError(msg)
|
|
174
|
+
|
|
129
175
|
@classmethod
|
|
130
176
|
def get_setup_format(cls, *, llm_format: bool) -> str:
|
|
131
177
|
"""Gets the JSON schema of the setup format model.
|
|
@@ -143,6 +189,18 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretMode
|
|
|
143
189
|
msg = "'%s' class does not define an 'setup_format'."
|
|
144
190
|
raise NotImplementedError(msg)
|
|
145
191
|
|
|
192
|
+
@classmethod
|
|
193
|
+
def create_config_setup_model(cls, config_setup_data: dict[str, Any]) -> ConfigSetupModelT:
|
|
194
|
+
"""Create the setup model from the setup data.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
config_setup_data: The setup data to create the model from.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
The setup model.
|
|
201
|
+
"""
|
|
202
|
+
return cls.config_setup_format(**config_setup_data)
|
|
203
|
+
|
|
146
204
|
@classmethod
|
|
147
205
|
def create_input_model(cls, input_data: dict[str, Any]) -> InputModelT:
|
|
148
206
|
"""Create the input model from the input data.
|
|
@@ -191,6 +249,21 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretMode
|
|
|
191
249
|
"""
|
|
192
250
|
return cls.output_format(**output_data)
|
|
193
251
|
|
|
252
|
+
@abstractmethod
|
|
253
|
+
async def run_config_setup(
|
|
254
|
+
self,
|
|
255
|
+
config_setup_data: ConfigSetupModelT,
|
|
256
|
+
setup_data: SetupModelT,
|
|
257
|
+
callback: Callable,
|
|
258
|
+
) -> None:
|
|
259
|
+
"""Run config setup the module.
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
OptionalFeatureNotImplementedError: If the config setup feature is not implemented.
|
|
263
|
+
"""
|
|
264
|
+
msg = f"'{self}' class does not define an optional 'run_config_setup' attribute."
|
|
265
|
+
raise OptionalFeatureNotImplementedError(msg)
|
|
266
|
+
|
|
194
267
|
@abstractmethod
|
|
195
268
|
async def initialize(self, setup_data: SetupModelT) -> None:
|
|
196
269
|
"""Initialize the module."""
|
|
@@ -223,30 +296,59 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretMode
|
|
|
223
296
|
asyncio.CancelledError: If the module is cancelled
|
|
224
297
|
"""
|
|
225
298
|
try:
|
|
299
|
+
logger.warning("Starting module %s", self.name)
|
|
226
300
|
await self.run(input_data, setup_data, callback)
|
|
227
|
-
|
|
301
|
+
logger.warning("Module %s finished", self.name)
|
|
228
302
|
except asyncio.CancelledError:
|
|
229
|
-
|
|
303
|
+
self._status = ModuleStatus.CANCELLED
|
|
304
|
+
logger.error(f"Module {self.name} cancelled")
|
|
230
305
|
except Exception:
|
|
231
306
|
self._status = ModuleStatus.FAILED
|
|
232
307
|
logger.exception("Error inside module %s", self.name)
|
|
233
308
|
else:
|
|
234
309
|
self._status = ModuleStatus.STOPPED
|
|
310
|
+
finally:
|
|
311
|
+
await self.stop()
|
|
235
312
|
|
|
236
313
|
async def start(
|
|
237
314
|
self,
|
|
238
315
|
input_data: InputModelT,
|
|
239
316
|
setup_data: SetupModelT,
|
|
240
|
-
callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
|
|
317
|
+
callback: Callable[[OutputModelT | ModuleErrorModel], Coroutine[Any, Any, None]],
|
|
318
|
+
done_callback: Callable | None = None,
|
|
241
319
|
) -> None:
|
|
242
320
|
"""Start the module."""
|
|
243
321
|
try:
|
|
322
|
+
logger.info("Inititalize module")
|
|
244
323
|
await self.initialize(setup_data=setup_data)
|
|
324
|
+
except Exception as e:
|
|
325
|
+
self._status = ModuleStatus.FAILED
|
|
326
|
+
short_description = "Error initializing module"
|
|
327
|
+
logger.exception("%s: %s", short_description, e)
|
|
328
|
+
await callback(
|
|
329
|
+
ModuleErrorModel(
|
|
330
|
+
code=str(self._status),
|
|
331
|
+
short_description=short_description,
|
|
332
|
+
exception=str(e),
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
if done_callback is not None:
|
|
336
|
+
await done_callback(None)
|
|
337
|
+
await self.stop()
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
logger.info("Run lifecycle")
|
|
245
342
|
self._status = ModuleStatus.RUNNING
|
|
246
|
-
self._task = asyncio.create_task(
|
|
343
|
+
self._task = asyncio.create_task(
|
|
344
|
+
self._run_lifecycle(input_data, setup_data, callback),
|
|
345
|
+
name="module_lifecycle",
|
|
346
|
+
)
|
|
347
|
+
if done_callback is not None:
|
|
348
|
+
self._task.add_done_callback(done_callback)
|
|
247
349
|
except Exception:
|
|
248
350
|
self._status = ModuleStatus.FAILED
|
|
249
|
-
logger.exception("Error
|
|
351
|
+
logger.exception("Error during module lifecyle")
|
|
250
352
|
|
|
251
353
|
async def stop(self) -> None:
|
|
252
354
|
"""Stop the module."""
|
|
@@ -263,3 +365,18 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretMode
|
|
|
263
365
|
except Exception:
|
|
264
366
|
self._status = ModuleStatus.FAILED
|
|
265
367
|
logger.exception("Error stopping module")
|
|
368
|
+
|
|
369
|
+
async def start_config_setup(
|
|
370
|
+
self,
|
|
371
|
+
config_setup_data: ConfigSetupModelT,
|
|
372
|
+
setup_data: SetupModelT,
|
|
373
|
+
callback: Callable[[OutputModelT | ModuleErrorModel], Coroutine[Any, Any, None]],
|
|
374
|
+
) -> None:
|
|
375
|
+
"""Start the module."""
|
|
376
|
+
try:
|
|
377
|
+
logger.info("Run Config Setup lifecycle")
|
|
378
|
+
self._status = ModuleStatus.RUNNING
|
|
379
|
+
await self.run_config_setup(config_setup_data, setup_data, callback)
|
|
380
|
+
except Exception:
|
|
381
|
+
self._status = ModuleStatus.FAILED
|
|
382
|
+
logger.exception("Error during module lifecyle")
|
|
@@ -3,8 +3,18 @@
|
|
|
3
3
|
from abc import ABC
|
|
4
4
|
|
|
5
5
|
from digitalkin.models.module import InputModelT, OutputModelT, SecretModelT, SetupModelT
|
|
6
|
+
from digitalkin.models.module.module_types import ConfigSetupModelT
|
|
6
7
|
from digitalkin.modules._base_module import BaseModule
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
class ArchetypeModule(
|
|
10
|
+
class ArchetypeModule(
|
|
11
|
+
BaseModule[
|
|
12
|
+
InputModelT,
|
|
13
|
+
OutputModelT,
|
|
14
|
+
SetupModelT,
|
|
15
|
+
SecretModelT,
|
|
16
|
+
ConfigSetupModelT,
|
|
17
|
+
],
|
|
18
|
+
ABC,
|
|
19
|
+
):
|
|
10
20
|
"""ArchetypeModule extends BaseModule to implement specific module types."""
|