digitalkin 0.1.1__py3-none-any.whl → 0.2.0__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 (77) 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 +124 -0
  6. base_server/server_async_secure.py +142 -0
  7. base_server/server_sync_insecure.py +102 -0
  8. base_server/server_sync_secure.py +121 -0
  9. digitalkin/__init__.py +1 -11
  10. digitalkin/__version__.py +1 -4
  11. digitalkin/{grpc → grpc_servers}/__init__.py +1 -13
  12. digitalkin/{grpc → grpc_servers}/_base_server.py +3 -3
  13. digitalkin/{grpc → grpc_servers}/module_server.py +30 -12
  14. digitalkin/{grpc → grpc_servers}/module_servicer.py +30 -14
  15. digitalkin/{grpc → grpc_servers}/registry_server.py +6 -4
  16. digitalkin/{grpc → grpc_servers}/registry_servicer.py +8 -2
  17. digitalkin/{grpc → grpc_servers}/utils/factory.py +6 -4
  18. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +68 -0
  19. digitalkin/{grpc → grpc_servers}/utils/models.py +1 -1
  20. digitalkin/models/__init__.py +1 -4
  21. digitalkin/models/module/__init__.py +8 -2
  22. digitalkin/models/module/module_types.py +10 -0
  23. digitalkin/models/services/__init__.py +0 -5
  24. digitalkin/modules/__init__.py +3 -3
  25. digitalkin/modules/_base_module.py +64 -27
  26. digitalkin/modules/archetype_module.py +2 -6
  27. digitalkin/modules/job_manager.py +46 -28
  28. digitalkin/modules/tool_module.py +3 -7
  29. digitalkin/modules/trigger_module.py +2 -7
  30. digitalkin/services/__init__.py +7 -9
  31. digitalkin/services/agent/__init__.py +2 -2
  32. digitalkin/services/agent/agent_strategy.py +3 -6
  33. digitalkin/services/agent/default_agent.py +1 -4
  34. digitalkin/services/base_strategy.py +18 -0
  35. digitalkin/services/cost/__init__.py +4 -3
  36. digitalkin/services/cost/cost_strategy.py +35 -5
  37. digitalkin/services/cost/default_cost.py +22 -5
  38. digitalkin/services/cost/grpc_cost.py +81 -0
  39. digitalkin/services/filesystem/__init__.py +4 -3
  40. digitalkin/services/filesystem/default_filesystem.py +197 -17
  41. digitalkin/services/filesystem/filesystem_strategy.py +54 -15
  42. digitalkin/services/filesystem/grpc_filesystem.py +209 -0
  43. digitalkin/services/identity/__init__.py +2 -2
  44. digitalkin/services/identity/default_identity.py +1 -1
  45. digitalkin/services/identity/identity_strategy.py +3 -1
  46. digitalkin/services/registry/__init__.py +2 -2
  47. digitalkin/services/registry/default_registry.py +1 -4
  48. digitalkin/services/registry/registry_strategy.py +3 -6
  49. digitalkin/services/services_config.py +176 -0
  50. digitalkin/services/services_models.py +61 -0
  51. digitalkin/services/setup/default_setup.py +222 -0
  52. digitalkin/services/setup/grpc_setup.py +307 -0
  53. digitalkin/services/setup/setup_strategy.py +145 -0
  54. digitalkin/services/snapshot/__init__.py +2 -2
  55. digitalkin/services/snapshot/default_snapshot.py +1 -1
  56. digitalkin/services/snapshot/snapshot_strategy.py +3 -4
  57. digitalkin/services/storage/__init__.py +4 -3
  58. digitalkin/services/storage/default_storage.py +184 -57
  59. digitalkin/services/storage/grpc_storage.py +76 -170
  60. digitalkin/services/storage/storage_strategy.py +195 -24
  61. digitalkin/utils/arg_parser.py +16 -17
  62. {digitalkin-0.1.1.dist-info → digitalkin-0.2.0.dist-info}/METADATA +8 -7
  63. digitalkin-0.2.0.dist-info/RECORD +78 -0
  64. {digitalkin-0.1.1.dist-info → digitalkin-0.2.0.dist-info}/WHEEL +1 -1
  65. digitalkin-0.2.0.dist-info/top_level.txt +3 -0
  66. modules/__init__.py +0 -0
  67. modules/minimal_llm_module.py +162 -0
  68. modules/storage_module.py +187 -0
  69. modules/text_transform_module.py +201 -0
  70. digitalkin/services/default_service.py +0 -13
  71. digitalkin/services/development_service.py +0 -10
  72. digitalkin/services/service_provider.py +0 -27
  73. digitalkin-0.1.1.dist-info/RECORD +0 -59
  74. digitalkin-0.1.1.dist-info/top_level.txt +0 -1
  75. /digitalkin/{grpc → grpc_servers}/utils/exceptions.py +0 -0
  76. /digitalkin/{grpc → grpc_servers}/utils/types.py +0 -0
  77. {digitalkin-0.1.1.dist-info → digitalkin-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -11,15 +11,15 @@ from typing import Any, cast
11
11
  import grpc
12
12
  from grpc import aio as grpc_aio
13
13
 
14
- from digitalkin.grpc.utils.exceptions import (
14
+ from digitalkin.grpc_servers.utils.exceptions import (
15
15
  ConfigurationError,
16
16
  ReflectionError,
17
17
  SecurityError,
18
18
  ServerStateError,
19
19
  ServicerError,
20
20
  )
21
- from digitalkin.grpc.utils.models import SecurityMode, ServerConfig, ServerMode
22
- from digitalkin.grpc.utils.types import GrpcServer, ServiceDescriptor, T
21
+ from digitalkin.grpc_servers.utils.models import SecurityMode, ServerConfig, ServerMode
22
+ from digitalkin.grpc_servers.utils.types import GrpcServer, ServiceDescriptor, T
23
23
 
24
24
  logger = logging.getLogger(__name__)
25
25
 
@@ -5,13 +5,16 @@ import uuid
5
5
 
6
6
  import grpc
7
7
 
8
- from digitalkin.grpc._base_server import BaseServer
9
- from digitalkin.grpc.module_servicer import ModuleServicer
10
- from digitalkin.grpc.utils.exceptions import ServerError
11
- from digitalkin.grpc.utils.models import ModuleServerConfig, SecurityMode
8
+ from digitalkin.grpc_servers._base_server import BaseServer
9
+ from digitalkin.grpc_servers.module_servicer import ModuleServicer
10
+ from digitalkin.grpc_servers.utils.exceptions import ServerError
11
+ from digitalkin.grpc_servers.utils.models import ModuleServerConfig, SecurityMode
12
12
  from digitalkin.modules._base_module import BaseModule
13
13
 
14
- from digitalkin_proto.digitalkin.module.v2 import module_service_pb2, module_service_pb2_grpc
14
+ from digitalkin_proto.digitalkin.module.v2 import (
15
+ module_service_pb2,
16
+ module_service_pb2_grpc,
17
+ )
15
18
  from digitalkin_proto.digitalkin.module_registry.v2 import (
16
19
  metadata_pb2,
17
20
  module_registry_service_pb2_grpc,
@@ -111,7 +114,9 @@ class ModuleServer(BaseServer):
111
114
  Raises:
112
115
  ServerError: If communication with the registry server fails.
113
116
  """
114
- logger.info("Registering module with registry at %s", self.config.registry_address)
117
+ logger.info(
118
+ "Registering module with registry at %s", self.config.registry_address
119
+ )
115
120
 
116
121
  # Create appropriate channel based on security mode
117
122
  channel = self._create_registry_channel()
@@ -125,11 +130,16 @@ class ModuleServer(BaseServer):
125
130
 
126
131
  metadata = metadata_pb2.Metadata(
127
132
  name=self.module_class.metadata["name"],
128
- tags=[metadata_pb2.Tag(tag=tag) for tag in self.module_class.metadata["tags"]],
133
+ tags=[
134
+ metadata_pb2.Tag(tag=tag)
135
+ for tag in self.module_class.metadata["tags"]
136
+ ],
129
137
  description=self.module_class.metadata["description"],
130
138
  )
131
139
 
132
- self.module_class.metadata["module_id"] = f"{self.module_class.metadata['name']}:{uuid.uuid4()}"
140
+ self.module_class.metadata["module_id"] = (
141
+ f"{self.module_class.metadata['name']}:{uuid.uuid4()}"
142
+ )
133
143
  # Create registration request
134
144
  request = registration_pb2.RegisterRequest(
135
145
  module_id=self.module_class.metadata["module_id"],
@@ -162,7 +172,9 @@ class ModuleServer(BaseServer):
162
172
  Raises:
163
173
  ServerError: If communication with the registry server fails.
164
174
  """
165
- logger.info("Deregistering module from registry at %s", self.config.registry_address)
175
+ logger.info(
176
+ "Deregistering module from registry at %s", self.config.registry_address
177
+ )
166
178
 
167
179
  # Create appropriate channel based on security mode
168
180
  channel = self._create_registry_channel()
@@ -204,13 +216,19 @@ class ModuleServer(BaseServer):
204
216
 
205
217
  root_certificates = None
206
218
  if self.config.credentials.root_cert_path:
207
- with open(self.config.credentials.root_cert_path, "rb") as root_cert_file: # noqa: FURB101
219
+ with open(
220
+ self.config.credentials.root_cert_path, "rb"
221
+ ) as root_cert_file: # noqa: FURB101
208
222
  root_certificates = root_cert_file.read()
209
223
 
210
224
  # Create channel credentials
211
- channel_credentials = grpc.ssl_channel_credentials(root_certificates=root_certificates or certificate_chain)
225
+ channel_credentials = grpc.ssl_channel_credentials(
226
+ root_certificates=root_certificates or certificate_chain
227
+ )
212
228
 
213
- return grpc.secure_channel(self.config.registry_address, channel_credentials)
229
+ return grpc.secure_channel(
230
+ self.config.registry_address, channel_credentials
231
+ )
214
232
  # Insecure channel
215
233
  return grpc.insecure_channel(self.config.registry_address)
216
234
 
@@ -14,9 +14,15 @@ from digitalkin_proto.digitalkin.module.v2 import (
14
14
  )
15
15
  from google.protobuf import json_format, struct_pb2
16
16
 
17
+ from digitalkin.grpc_servers.utils.exceptions import ServicerError
18
+ from digitalkin.models.module import OutputModelT
17
19
  from digitalkin.models.module.module import ModuleStatus
18
20
  from digitalkin.modules._base_module import BaseModule
19
21
  from digitalkin.modules.job_manager import JobManager
22
+ from digitalkin.services.services_models import ServicesMode
23
+ from digitalkin.services.setup.default_setup import DefaultSetup
24
+ from digitalkin.services.setup.grpc_setup import GrpcSetup
25
+ from digitalkin.services.setup.setup_strategy import SetupStrategy
20
26
 
21
27
  logger = logging.getLogger(__name__)
22
28
 
@@ -31,6 +37,8 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer):
31
37
  active_jobs: Dictionary tracking active module jobs.
32
38
  """
33
39
 
40
+ setup: SetupStrategy
41
+
34
42
  def __init__(self, module_class: type[BaseModule]) -> None:
35
43
  """Initialize the module servicer.
36
44
 
@@ -41,8 +49,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer):
41
49
  self.queue: asyncio.Queue = asyncio.Queue()
42
50
  self.module_class = module_class
43
51
  self.job_manager = JobManager(module_class)
52
+ self.setup = GrpcSetup() if self.job_manager.args.services_mode == ServicesMode.REMOTE else DefaultSetup()
44
53
 
45
- async def add_to_queue(self, job_id: str, output_data: dict[str, Any]) -> None:
54
+ async def add_to_queue(self, job_id: str, output_data: OutputModelT) -> None:
46
55
  """Callback used to add the output data to the queue of messages."""
47
56
  logger.info("JOB: %s added an output_data: %s", job_id, output_data)
48
57
  await self.queue.put({job_id: output_data})
@@ -60,19 +69,26 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer):
60
69
 
61
70
  Yields:
62
71
  Responses during module execution.
72
+
73
+ Raises:
74
+ ServicerError: the necessary query didn't work.
63
75
  """
64
76
  logger.info("StartModule called for module: '%s'", self.module_class.__name__)
65
77
  # Process the module input
66
- input_data = dict(request.input.items())
67
- setup_data = self.module_class.storage.get(
68
- table="setups",
69
- data={
70
- "keys": [
71
- # remove prefix 'setups:'
72
- request.setup_id[7:],
73
- ],
74
- },
75
- )[0]
78
+ # TODO: Check failure of input data format
79
+ input_data = self.module_class.input_format(**dict(request.input.items()))
80
+ setup_data_class = self.setup.get_setup(
81
+ setup_dict={
82
+ "setup_id": request.setup_id,
83
+ "mission_id": "missions:test_demo",
84
+ }
85
+ )
86
+
87
+ if not setup_data_class:
88
+ msg = "No setup data returned."
89
+ raise ServicerError(msg)
90
+ # TODO: Check failure of setup data format
91
+ setup_data = self.module_class.setup_format(**setup_data_class.current_setup_version.content)
76
92
 
77
93
  # setup_id should be use to request a precise setup from the module
78
94
  # Create a job for this execution
@@ -84,9 +100,9 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer):
84
100
 
85
101
  while module.status == ModuleStatus.RUNNING or not self.queue.empty():
86
102
  output_data = await self.queue.get()
87
- if output_data.get("error", None) is not None:
88
- context.set_code(output_data["error"]["code"])
89
- context.set_details(output_data["error"]["error_message"])
103
+ if output_data[job_id].get("error", None) is not None:
104
+ context.set_code(output_data[job_id]["error"]["code"])
105
+ context.set_details(output_data[job_id]["error"]["error_message"])
90
106
  yield lifecycle_pb2.StartModuleResponse(success=False)
91
107
  return
92
108
  else:
@@ -2,11 +2,13 @@
2
2
 
3
3
  import logging
4
4
 
5
- from digitalkin_proto.digitalkin.module_registry.v2 import module_registry_service_pb2_grpc
5
+ from digitalkin_proto.digitalkin.module_registry.v2 import (
6
+ module_registry_service_pb2_grpc,
7
+ )
6
8
 
7
- from digitalkin.grpc._base_server import BaseServer
8
- from digitalkin.grpc.registry_servicer import RegistryModule, RegistryServicer
9
- from digitalkin.grpc.utils.models import RegistryServerConfig
9
+ from digitalkin.grpc_servers._base_server import BaseServer
10
+ from digitalkin.grpc_servers.registry_servicer import RegistryModule, RegistryServicer
11
+ from digitalkin.grpc_servers.utils.models import RegistryServerConfig
10
12
 
11
13
  logger = logging.getLogger(__name__)
12
14
 
@@ -88,7 +88,9 @@ class Metadata(BaseModel):
88
88
  metadata_pb2.Metadata: The protobuf representation of this metadata.
89
89
  """
90
90
  return metadata_pb2.Metadata(
91
- name=self.name, tags=(t.to_proto() for t in self.tags), description=self.description
91
+ name=self.name,
92
+ tags=(t.to_proto() for t in self.tags),
93
+ description=self.description,
92
94
  )
93
95
 
94
96
  @classmethod
@@ -361,7 +363,11 @@ class RegistryServicer(module_registry_service_pb2_grpc.ModuleRegistryServiceSer
361
363
  Returns:
362
364
  status_pb2.ListModulesStatusResponse: A response containing a list of module statuses.
363
365
  """
364
- logger.info("Getting registered modules with offset %d and limit %d", request.offset, request.list_size)
366
+ logger.info(
367
+ "Getting registered modules with offset %d and limit %d",
368
+ request.offset,
369
+ request.list_size,
370
+ )
365
371
  if request.offset > len(self.registered_modules):
366
372
  message = f"Out of range {request.offset}"
367
373
  logger.warning(message)
@@ -3,9 +3,9 @@
3
3
  from pathlib import Path
4
4
  from typing import Any
5
5
 
6
- from digitalkin.grpc.module_server import ModuleServer
7
- from digitalkin.grpc.registry_server import RegistryServer
8
- from digitalkin.grpc.utils.models import (
6
+ from digitalkin.grpc_servers.module_server import ModuleServer
7
+ from digitalkin.grpc_servers.registry_server import RegistryServer
8
+ from digitalkin.grpc_servers.utils.models import (
9
9
  ModuleServerConfig,
10
10
  RegistryServerConfig,
11
11
  SecurityMode,
@@ -167,7 +167,9 @@ def _create_server_config(
167
167
  # Add credentials if secure mode
168
168
  if security == "secure":
169
169
  if not server_key_path or not server_cert_path:
170
- raise ValueError("Server key and certificate paths are required for secure mode")
170
+ raise ValueError(
171
+ "Server key and certificate paths are required for secure mode"
172
+ )
171
173
 
172
174
  config_params["credentials"] = ServerCredentials(
173
175
  server_key_path=Path(server_key_path),
@@ -0,0 +1,68 @@
1
+ """Client wrapper to ease channel creation with specific ServerConfig."""
2
+
3
+ import logging
4
+ from typing import Any
5
+
6
+ import grpc
7
+
8
+ from digitalkin.grpc_servers.utils.exceptions import ServerError
9
+ from digitalkin.grpc_servers.utils.models import SecurityMode, ServerConfig
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class GrpcClientWrapper:
15
+ """gRPC client shared by the different services."""
16
+
17
+ stub: Any
18
+
19
+ @staticmethod
20
+ def _init_channel(config: ServerConfig) -> grpc.Channel:
21
+ """Create an appropriate channel to the registry server.
22
+
23
+ Returns:
24
+ A gRPC channel for communication with the registry.
25
+
26
+ Raises:
27
+ ValueError: If credentials are required but not provided.
28
+ """
29
+ if config.security == SecurityMode.SECURE and config.credentials:
30
+ # Secure channel
31
+ with open(config.credentials.server_cert_path, "rb") as cert_file: # noqa: FURB101
32
+ certificate_chain = cert_file.read()
33
+
34
+ root_certificates = None
35
+ if config.credentials.root_cert_path:
36
+ with open(config.credentials.root_cert_path, "rb") as root_cert_file: # noqa: FURB101
37
+ root_certificates = root_cert_file.read()
38
+
39
+ # Create channel credentials
40
+ channel_credentials = grpc.ssl_channel_credentials(root_certificates=root_certificates or certificate_chain)
41
+
42
+ return grpc.secure_channel(f"{config.host}:{config.port}", channel_credentials)
43
+ # Insecure channel
44
+ return grpc.insecure_channel(f"{config.host}:{config.port}")
45
+
46
+ def exec_grpc_query(self, query_endpoint: str, request: Any) -> Any: # noqa: ANN401
47
+ """Execute a gRPC query with from the query's rpc endpoint name.
48
+
49
+ Arguments:
50
+ query_endpoint: rpc query name
51
+ request: gRPC object to match the rpc query
52
+
53
+ Returns:
54
+ corresponding gRPC reponse.
55
+
56
+ Raises:
57
+ ServerError: gRPC error catching
58
+ """
59
+ try:
60
+ # Call the register method
61
+ logger.warning("send request to %s", query_endpoint)
62
+ response = getattr(self.stub, query_endpoint)(request)
63
+ logger.warning("recive response from request to registry: %s", response)
64
+ except grpc.RpcError:
65
+ logger.exception("RPC error during registration:")
66
+ raise ServerError
67
+ else:
68
+ return response
@@ -6,7 +6,7 @@ from typing import Any
6
6
 
7
7
  from pydantic import BaseModel, Field, ValidationInfo, field_validator
8
8
 
9
- from digitalkin.grpc.utils.exceptions import ConfigurationError, SecurityError
9
+ from digitalkin.grpc_servers.utils.exceptions import ConfigurationError, SecurityError
10
10
 
11
11
 
12
12
  class ServerMode(str, Enum):
@@ -1,11 +1,8 @@
1
1
  """This package contains the models for DigitalKin."""
2
2
 
3
- from .module import Module, ModuleStatus
4
- from .services import CostEvent, StorageModel
3
+ from digitalkin.models.module import Module, ModuleStatus
5
4
 
6
5
  __all__ = [
7
- "CostEvent",
8
6
  "Module",
9
7
  "ModuleStatus",
10
- "StorageModel",
11
8
  ]
@@ -1,5 +1,11 @@
1
1
  """This module contains the models for the modules."""
2
2
 
3
- from .module import Module, ModuleStatus
3
+ from digitalkin.models.module.module import Module, ModuleStatus
4
+ from digitalkin.models.module.module_types import (
5
+ InputModelT,
6
+ OutputModelT,
7
+ SecretModelT,
8
+ SetupModelT,
9
+ )
4
10
 
5
- __all__ = ["Module", "ModuleStatus"]
11
+ __all__ = ["InputModelT", "Module", "ModuleStatus", "OutputModelT", "SecretModelT", "SetupModelT"]
@@ -0,0 +1,10 @@
1
+ """Types for module models."""
2
+
3
+ from typing import TypeVar
4
+
5
+ from pydantic import BaseModel
6
+
7
+ InputModelT = TypeVar("InputModelT", bound=BaseModel)
8
+ OutputModelT = TypeVar("OutputModelT", bound=BaseModel)
9
+ SetupModelT = TypeVar("SetupModelT", bound=BaseModel)
10
+ SecretModelT = TypeVar("SecretModelT", bound=BaseModel)
@@ -1,6 +1 @@
1
1
  """This module contains the models for the services."""
2
-
3
- from .cost import CostEvent
4
- from .storage import StorageModel
5
-
6
- __all__ = ["CostEvent", "StorageModel"]
@@ -1,7 +1,7 @@
1
1
  """Module package for DigitalKin."""
2
2
 
3
- from .archetype_module import ArchetypeModule
4
- from .tool_module import ToolModule
5
- from .trigger_module import TriggerModule
3
+ from digitalkin.modules.archetype_module import ArchetypeModule
4
+ from digitalkin.modules.tool_module import ToolModule
5
+ from digitalkin.modules.trigger_module import TriggerModule
6
6
 
7
7
  __all__ = ["ArchetypeModule", "ToolModule", "TriggerModule"]
@@ -4,44 +4,64 @@ import asyncio
4
4
  import contextlib
5
5
  import json
6
6
  from abc import ABC, abstractmethod
7
- from collections.abc import Callable
8
- from typing import Any, ClassVar, Generic, TypeVar
9
-
10
- from pydantic import BaseModel
7
+ from collections.abc import Callable, Coroutine
8
+ from typing import Any, ClassVar, Generic
11
9
 
12
10
  from digitalkin.logger import logger
13
- from digitalkin.models.module import ModuleStatus
14
- from digitalkin.services.service_provider import ServiceProvider
11
+ from digitalkin.models.module import InputModelT, ModuleStatus, OutputModelT, SecretModelT, SetupModelT
12
+ from digitalkin.services.agent.agent_strategy import AgentStrategy
13
+ from digitalkin.services.cost.cost_strategy import CostStrategy
14
+ from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
15
+ from digitalkin.services.identity.identity_strategy import IdentityStrategy
16
+ from digitalkin.services.registry.registry_strategy import RegistryStrategy
17
+ from digitalkin.services.services_config import ServicesConfig, ServicesStrategy
18
+ from digitalkin.services.snapshot.snapshot_strategy import SnapshotStrategy
15
19
  from digitalkin.services.storage.storage_strategy import StorageStrategy
16
20
 
17
- InputModelT = TypeVar("InputModelT", bound=BaseModel)
18
- OutputModelT = TypeVar("OutputModelT", bound=BaseModel)
19
- SetupModelT = TypeVar("SetupModelT", bound=BaseModel)
20
-
21
21
 
22
- class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
22
+ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretModelT]):
23
23
  """BaseModule is the abstract base for all modules in the DigitalKin SDK."""
24
24
 
25
+ name: str
26
+ description: str
25
27
  input_format: type[InputModelT]
26
28
  output_format: type[OutputModelT]
27
29
  setup_format: type[SetupModelT]
30
+ secret_format: type[SecretModelT]
28
31
  metadata: ClassVar[dict[str, Any]]
29
32
 
30
- local_services: type[ServiceProvider]
31
- dev_services: type[ServiceProvider]
32
-
33
+ # service config params
34
+ services_config_strategies: ClassVar[dict[str, ServicesStrategy | None]]
35
+ services_config_params: ClassVar[dict[str, dict[str, Any | None] | None]]
36
+ services_config: ServicesConfig
37
+
38
+ # services list
39
+ agent: AgentStrategy
40
+ cost: CostStrategy
41
+ filesystem: FilesystemStrategy
42
+ identity: IdentityStrategy
43
+ registry: RegistryStrategy
44
+ snapshot: SnapshotStrategy
33
45
  storage: StorageStrategy
34
46
 
47
+ def _init_strategies(self) -> None:
48
+ """Initialize the services configuration."""
49
+ for service_name in self.services_config.valid_strategy_names():
50
+ service = self.services_config.init_strategy(service_name, self.mission_id)
51
+ setattr(self, service_name, service)
52
+
35
53
  def __init__(
36
54
  self,
37
55
  job_id: str,
38
- name: str | None = None,
56
+ mission_id: str,
39
57
  ) -> None:
40
58
  """Initialize the module."""
41
59
  self.job_id: str = job_id
42
- self.name = name or self.__class__.__name__
60
+ self.mission_id: str = mission_id
43
61
  self._status = ModuleStatus.CREATED
44
62
  self._task: asyncio.Task | None = None
63
+ # Initialize services configuration
64
+ self._init_strategies()
45
65
 
46
66
  @property
47
67
  def status(self) -> ModuleStatus:
@@ -52,6 +72,23 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
52
72
  """
53
73
  return self._status
54
74
 
75
+ @classmethod
76
+ def get_secret_format(cls, llm_format: bool) -> str: # noqa: FBT001
77
+ """Get the JSON schema of the secret format model.
78
+
79
+ Raises:
80
+ NotImplementedError: If the `secret_format` is not defined.
81
+
82
+ Returns:
83
+ The JSON schema of the secret format as a string.
84
+ """
85
+ if cls.secret_format is not None:
86
+ if llm_format:
87
+ return json.dumps(cls.secret_format, indent=2)
88
+ return json.dumps(cls.secret_format.model_json_schema(), indent=2)
89
+ msg = f"{cls.__name__}' class does not define a 'secret_format'."
90
+ raise NotImplementedError(msg)
91
+
55
92
  @classmethod
56
93
  def get_input_format(cls, llm_format: bool) -> str: # noqa: FBT001
57
94
  """Get the JSON schema of the input format model.
@@ -62,7 +99,7 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
62
99
  Returns:
63
100
  The JSON schema of the input format as a string.
64
101
  """
65
- if cls.output_format is not None:
102
+ if cls.input_format is not None:
66
103
  if llm_format:
67
104
  return json.dumps(cls.input_format, indent=2)
68
105
  return json.dumps(cls.input_format.model_json_schema(), indent=2)
@@ -104,16 +141,16 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
104
141
  raise NotImplementedError(msg)
105
142
 
106
143
  @abstractmethod
107
- async def initialize(self, setup_data: dict[str, Any]) -> None:
144
+ async def initialize(self, setup_data: SetupModelT) -> None:
108
145
  """Initialize the module."""
109
146
  raise NotImplementedError
110
147
 
111
148
  @abstractmethod
112
149
  async def run(
113
150
  self,
114
- input_data: dict[str, Any],
115
- setup_data: dict[str, Any],
116
- callback: Callable,
151
+ input_data: InputModelT,
152
+ setup_data: SetupModelT,
153
+ callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
117
154
  ) -> None:
118
155
  """Run the module."""
119
156
  raise NotImplementedError
@@ -125,9 +162,9 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
125
162
 
126
163
  async def _run_lifecycle(
127
164
  self,
128
- input_data: dict[str, Any],
129
- setup_data: dict[str, Any],
130
- callback: Callable,
165
+ input_data: InputModelT,
166
+ setup_data: SetupModelT,
167
+ callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
131
168
  ) -> None:
132
169
  """Run the module lifecycle.
133
170
 
@@ -147,9 +184,9 @@ class BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT]):
147
184
 
148
185
  async def start(
149
186
  self,
150
- input_data: dict[str, Any],
151
- setup_data: dict[str, Any],
152
- callback: Callable,
187
+ input_data: InputModelT,
188
+ setup_data: SetupModelT,
189
+ callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
153
190
  ) -> None:
154
191
  """Start the module."""
155
192
  try:
@@ -2,13 +2,9 @@
2
2
 
3
3
  from abc import ABC
4
4
 
5
+ from digitalkin.models.module import InputModelT, OutputModelT, SecretModelT, SetupModelT
5
6
  from digitalkin.modules._base_module import BaseModule
6
7
 
7
8
 
8
- class ArchetypeModule(BaseModule, ABC):
9
+ class ArchetypeModule(BaseModule[InputModelT, OutputModelT, SetupModelT, SecretModelT], ABC):
9
10
  """ArchetypeModule extends BaseModule to implement specific module types."""
10
-
11
- def __init__(self, name: str | None = None) -> None:
12
- """Initialize the module with the given metadata."""
13
- super().__init__(self.job_id, name=name)
14
- self.capabilities = ["archetype"]