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.
Files changed (42) hide show
  1. digitalkin/__version__.py +1 -1
  2. digitalkin/grpc_servers/_base_server.py +15 -17
  3. digitalkin/grpc_servers/module_server.py +9 -10
  4. digitalkin/grpc_servers/module_servicer.py +199 -85
  5. digitalkin/grpc_servers/registry_server.py +3 -6
  6. digitalkin/grpc_servers/registry_servicer.py +18 -19
  7. digitalkin/grpc_servers/utils/exceptions.py +4 -0
  8. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +3 -5
  9. digitalkin/logger.py +45 -1
  10. digitalkin/models/module/__init__.py +2 -1
  11. digitalkin/models/module/module.py +1 -0
  12. digitalkin/models/module/module_types.py +1 -0
  13. digitalkin/modules/_base_module.py +124 -7
  14. digitalkin/modules/archetype_module.py +11 -1
  15. digitalkin/modules/job_manager/base_job_manager.py +181 -0
  16. digitalkin/modules/job_manager/job_manager_models.py +44 -0
  17. digitalkin/modules/job_manager/single_job_manager.py +285 -0
  18. digitalkin/modules/job_manager/taskiq_broker.py +214 -0
  19. digitalkin/modules/job_manager/taskiq_job_manager.py +286 -0
  20. digitalkin/modules/tool_module.py +2 -1
  21. digitalkin/modules/trigger_module.py +3 -1
  22. digitalkin/services/cost/default_cost.py +8 -4
  23. digitalkin/services/cost/grpc_cost.py +15 -7
  24. digitalkin/services/filesystem/default_filesystem.py +2 -4
  25. digitalkin/services/filesystem/grpc_filesystem.py +8 -5
  26. digitalkin/services/setup/__init__.py +1 -0
  27. digitalkin/services/setup/default_setup.py +10 -12
  28. digitalkin/services/setup/grpc_setup.py +8 -10
  29. digitalkin/services/storage/default_storage.py +11 -5
  30. digitalkin/services/storage/grpc_storage.py +23 -8
  31. digitalkin/utils/arg_parser.py +5 -48
  32. digitalkin/utils/development_mode_action.py +51 -0
  33. {digitalkin-0.2.12.dist-info → digitalkin-0.2.14.dist-info}/METADATA +46 -15
  34. {digitalkin-0.2.12.dist-info → digitalkin-0.2.14.dist-info}/RECORD +41 -34
  35. {digitalkin-0.2.12.dist-info → digitalkin-0.2.14.dist-info}/WHEEL +1 -1
  36. modules/cpu_intensive_module.py +281 -0
  37. modules/minimal_llm_module.py +240 -58
  38. modules/storage_module.py +5 -6
  39. modules/text_transform_module.py +1 -1
  40. digitalkin/modules/job_manager.py +0 -177
  41. {digitalkin-0.2.12.dist-info → digitalkin-0.2.14.dist-info}/licenses/LICENSE +0 -0
  42. {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.info("Registering registry servicer")
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.info("Registered registry servicer")
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 = logging.getLogger(__name__)
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.info("Registering module: %s", module_id)
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.info("Module %s registered at %s:%d", module_id, request.address, request.port)
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.info("Deregistering module: %s", module_id)
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.info("Module %s deregistered", module_id)
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.info("Discovering module: %s", request.module_id)
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.info("Discovering modules with criteria:")
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.info("%s", list(results))
295
+ logger.debug("%s", list(results))
297
296
  # Filter by name if specified
298
297
  if request.name:
299
- logger.info("\tname %s", request.name)
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.info("\tmodule_type %s", request.module_type)
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.info("\ttags %s", request.tags)
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.info("Found %d matching modules", len(results))
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.info("Getting status for module: %s", request.module_id)
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.info(
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.info("Found %d registered modules", len(modules_statuses))
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.info("Streaming all %d registered modules", len(self.registered_modules))
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.info("Updating status for module: %s to %s", module_id, request.status)
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.info("Status for module %s updated to %s", module_id, request.status)
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.warning("send request to %s", query_endpoint)
65
+ logger.debug("send request to %s", query_endpoint)
68
66
  response = getattr(self.stub, query_endpoint)(request)
69
- logger.warning("recive response from request to registry: %s", response)
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"]
@@ -14,6 +14,7 @@ class ModuleStatus(Enum):
14
14
  STOPPING = auto() # Module is stopping
15
15
  STOPPED = auto() # Module stop successfuly
16
16
  FAILED = auto() # Module stopped due to internal error
17
+ CANCELLED = auto() # Module stopped due to internal error
17
18
  NOT_FOUND = auto()
18
19
 
19
20
 
@@ -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 InputModelT, ModuleStatus, OutputModelT, SecretModelT, SetupModelT
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 BaseModule(ABC, Generic[InputModelT, OutputModelT, SetupModelT, SecretModelT]):
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
- await self.stop()
301
+ logger.warning("Module %s finished", self.name)
228
302
  except asyncio.CancelledError:
229
- logger.info(f"Module {self.name} cancelled")
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(self._run_lifecycle(input_data, setup_data, callback))
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 starting module")
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(BaseModule[InputModelT, OutputModelT, SetupModelT, SecretModelT], ABC):
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."""