digitalkin 0.2.15__tar.gz → 0.2.17__tar.gz

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 (97) hide show
  1. {digitalkin-0.2.15 → digitalkin-0.2.17}/PKG-INFO +1 -1
  2. {digitalkin-0.2.15 → digitalkin-0.2.17}/pyproject.toml +2 -2
  3. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/__version__.py +1 -1
  4. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/module_server.py +6 -6
  5. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/module_servicer.py +1 -0
  6. digitalkin-0.2.17/src/digitalkin/models/module/__init__.py +26 -0
  7. digitalkin-0.2.17/src/digitalkin/models/module/module_context.py +24 -0
  8. digitalkin-0.2.17/src/digitalkin/models/module/module_types.py +43 -0
  9. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/modules/__init__.py +2 -2
  10. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/modules/_base_module.py +78 -8
  11. digitalkin-0.2.17/src/digitalkin/modules/trigger_handler.py +47 -0
  12. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/filesystem/default_filesystem.py +12 -2
  13. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/filesystem/filesystem_strategy.py +37 -4
  14. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/filesystem/grpc_filesystem.py +15 -6
  15. digitalkin-0.2.17/src/digitalkin/utils/package_discover.py +358 -0
  16. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin.egg-info/PKG-INFO +1 -1
  17. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin.egg-info/SOURCES.txt +4 -2
  18. digitalkin-0.2.15/src/digitalkin/models/module/__init__.py +0 -12
  19. digitalkin-0.2.15/src/digitalkin/models/module/module_types.py +0 -11
  20. digitalkin-0.2.15/src/digitalkin/modules/trigger_module.py +0 -11
  21. {digitalkin-0.2.15 → digitalkin-0.2.17}/LICENSE +0 -0
  22. {digitalkin-0.2.15 → digitalkin-0.2.17}/README.md +0 -0
  23. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/base_server/__init__.py +0 -0
  24. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/base_server/mock/__init__.py +0 -0
  25. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/base_server/mock/mock_pb2.py +0 -0
  26. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/base_server/mock/mock_pb2_grpc.py +0 -0
  27. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/base_server/server_async_insecure.py +0 -0
  28. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/base_server/server_async_secure.py +0 -0
  29. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/base_server/server_sync_insecure.py +0 -0
  30. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/base_server/server_sync_secure.py +0 -0
  31. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/modules/__init__.py +0 -0
  32. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/modules/cpu_intensive_module.py +0 -0
  33. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/modules/minimal_llm_module.py +0 -0
  34. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/modules/text_transform_module.py +0 -0
  35. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/services/filesystem_module.py +0 -0
  36. {digitalkin-0.2.15 → digitalkin-0.2.17}/examples/services/storage_module.py +0 -0
  37. {digitalkin-0.2.15 → digitalkin-0.2.17}/setup.cfg +0 -0
  38. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/__init__.py +0 -0
  39. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/__init__.py +0 -0
  40. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/_base_server.py +0 -0
  41. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/registry_server.py +0 -0
  42. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/registry_servicer.py +0 -0
  43. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/utils/exceptions.py +0 -0
  44. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/utils/factory.py +0 -0
  45. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/utils/grpc_client_wrapper.py +0 -0
  46. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/utils/models.py +0 -0
  47. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/grpc_servers/utils/types.py +0 -0
  48. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/logger.py +0 -0
  49. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/models/__init__.py +0 -0
  50. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/models/module/module.py +0 -0
  51. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/models/services/__init__.py +0 -0
  52. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/models/services/cost.py +0 -0
  53. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/models/services/storage.py +0 -0
  54. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/modules/archetype_module.py +0 -0
  55. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/modules/job_manager/base_job_manager.py +0 -0
  56. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/modules/job_manager/job_manager_models.py +0 -0
  57. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/modules/job_manager/single_job_manager.py +0 -0
  58. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/modules/job_manager/taskiq_broker.py +0 -0
  59. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/modules/job_manager/taskiq_job_manager.py +0 -0
  60. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/modules/tool_module.py +0 -0
  61. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/py.typed +0 -0
  62. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/__init__.py +0 -0
  63. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/agent/__init__.py +0 -0
  64. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/agent/agent_strategy.py +0 -0
  65. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/agent/default_agent.py +0 -0
  66. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/base_strategy.py +0 -0
  67. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/cost/__init__.py +0 -0
  68. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/cost/cost_strategy.py +0 -0
  69. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/cost/default_cost.py +0 -0
  70. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/cost/grpc_cost.py +0 -0
  71. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/filesystem/__init__.py +0 -0
  72. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/identity/__init__.py +0 -0
  73. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/identity/default_identity.py +0 -0
  74. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/identity/identity_strategy.py +0 -0
  75. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/registry/__init__.py +0 -0
  76. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/registry/default_registry.py +0 -0
  77. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/registry/registry_strategy.py +0 -0
  78. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/services_config.py +0 -0
  79. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/services_models.py +0 -0
  80. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/setup/__init__.py +0 -0
  81. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/setup/default_setup.py +0 -0
  82. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/setup/grpc_setup.py +0 -0
  83. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/setup/setup_strategy.py +0 -0
  84. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/snapshot/__init__.py +0 -0
  85. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/snapshot/default_snapshot.py +0 -0
  86. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/snapshot/snapshot_strategy.py +0 -0
  87. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/storage/__init__.py +0 -0
  88. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/storage/default_storage.py +0 -0
  89. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/storage/grpc_storage.py +0 -0
  90. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/services/storage/storage_strategy.py +0 -0
  91. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/utils/__init__.py +0 -0
  92. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/utils/arg_parser.py +0 -0
  93. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/utils/development_mode_action.py +0 -0
  94. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin/utils/llm_ready_schema.py +0 -0
  95. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin.egg-info/dependency_links.txt +0 -0
  96. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin.egg-info/requires.txt +0 -0
  97. {digitalkin-0.2.15 → digitalkin-0.2.17}/src/digitalkin.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digitalkin
3
- Version: 0.2.15
3
+ Version: 0.2.17
4
4
  Summary: SDK to build kin used in DigitalKin
5
5
  Author-email: "DigitalKin.ai" <contact@digitalkin.ai>
6
6
  License: Attribution-NonCommercial-ShareAlike 4.0 International
@@ -12,7 +12,7 @@
12
12
 
13
13
  keywords = [ "digitalkin", "kin", "agent", "gprc", "sdk" ]
14
14
  # Version of the package automatically updated by bump2version (that is why it is separated)
15
- version = "0.2.15"
15
+ version = "0.2.17"
16
16
 
17
17
  classifiers = [
18
18
  "Development Status :: 3 - Alpha",
@@ -212,7 +212,7 @@
212
212
  "ANN401",
213
213
  "PLR0912",
214
214
  "ANN201",
215
- "D100"
215
+ "D100",
216
216
  ]
217
217
 
218
218
  [tool.ruff.lint.pylint]
@@ -5,4 +5,4 @@ from importlib.metadata import PackageNotFoundError, version
5
5
  try:
6
6
  __version__ = version("digitalkin")
7
7
  except PackageNotFoundError:
8
- __version__ = "0.2.15"
8
+ __version__ = "0.2.17"
@@ -79,10 +79,10 @@ class ModuleServer(BaseServer):
79
79
 
80
80
  def start(self) -> None:
81
81
  """Start the module server and register with the registry if configured."""
82
- logger.critical(self.server_config)
82
+ logger.info(self.server_config)
83
83
  super().start()
84
84
 
85
- logger.critical(self.server_config)
85
+ logger.info(self.server_config)
86
86
  # If a registry address is provided, register the module
87
87
  if self.server_config.registry_address:
88
88
  try:
@@ -91,17 +91,17 @@ class ModuleServer(BaseServer):
91
91
  logger.exception("Failed to register with registry")
92
92
 
93
93
  if self.module_servicer is not None:
94
- logger.critical(
94
+ logger.info(
95
95
  "Setup post init started with config: %s", self.client_config
96
96
  )
97
97
  self.module_servicer.setup.__post_init__(self.client_config)
98
98
 
99
99
  async def start_async(self) -> None:
100
100
  """Start the module server and register with the registry if configured."""
101
- logger.critical(self.server_config)
101
+ logger.info(self.server_config)
102
102
  await super().start_async()
103
103
 
104
- logger.critical(self.server_config)
104
+ logger.info(self.server_config)
105
105
  # If a registry address is provided, register the module
106
106
  if self.server_config.registry_address:
107
107
  try:
@@ -110,7 +110,7 @@ class ModuleServer(BaseServer):
110
110
  logger.exception("Failed to register with registry")
111
111
 
112
112
  if self.module_servicer is not None:
113
- logger.critical(
113
+ logger.info(
114
114
  "Setup post init started with config: %s", self.client_config
115
115
  )
116
116
  await self.module_servicer.job_manager._start()
@@ -70,6 +70,7 @@ class ModuleServicer(module_service_pb2_grpc.ModuleServiceServicer, ArgParser):
70
70
  module_class: The module type to serve.
71
71
  """
72
72
  super().__init__()
73
+ module_class.discover()
73
74
  self.module_class = module_class
74
75
  job_manager_class = self.args.job_manager_mode.get_manager_class()
75
76
  self.job_manager = job_manager_class(module_class, self.args.services_mode)
@@ -0,0 +1,26 @@
1
+ """This module contains the models for the modules."""
2
+
3
+ from digitalkin.models.module.module import Module, ModuleStatus
4
+ from digitalkin.models.module.module_context import ModuleContext
5
+ from digitalkin.models.module.module_types import (
6
+ ConfigSetupModelT,
7
+ InputModel,
8
+ InputModelT,
9
+ InputTrigger,
10
+ OutputModelT,
11
+ SecretModelT,
12
+ SetupModelT,
13
+ )
14
+
15
+ __all__ = [
16
+ "ConfigSetupModelT",
17
+ "InputModel",
18
+ "InputModelT",
19
+ "InputTrigger",
20
+ "Module",
21
+ "ModuleContext",
22
+ "ModuleStatus",
23
+ "OutputModelT",
24
+ "SecretModelT",
25
+ "SetupModelT",
26
+ ]
@@ -0,0 +1,24 @@
1
+ """Define the module context used in the triggers."""
2
+
3
+ from types import SimpleNamespace
4
+
5
+ from digitalkin.services.cost.cost_strategy import CostStrategy
6
+ from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
7
+ from digitalkin.services.storage.storage_strategy import StorageStrategy
8
+
9
+
10
+ class ModuleContext(SimpleNamespace):
11
+ """ModuleContext provides a container for strategies and resources used by a module.
12
+
13
+ Attributes:
14
+ cost (CostStrategy): The strategy used to calculate or manage costs within the module.
15
+ filesystem (FilesystemStrategy): The strategy for interacting with the filesystem.
16
+ storage (StorageStrategy): The strategy for handling storage operations.
17
+
18
+ This context object is designed to be passed to module components, providing them with
19
+ access to shared strategies and resources. Additional attributes may be set dynamically.
20
+ """
21
+
22
+ cost: CostStrategy
23
+ filesystem: FilesystemStrategy
24
+ storage: StorageStrategy
@@ -0,0 +1,43 @@
1
+ """Types for module models."""
2
+
3
+ from typing import TypeVar
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class InputTrigger(BaseModel):
9
+ """Defines the root input model exposing the protocol.
10
+
11
+ The mandatory protocol is important to define the module beahvior following the user or agent input.
12
+
13
+ Example:
14
+ class MyInput(InputModel):
15
+ root: InputTrigger
16
+ user_define_data: Any
17
+
18
+ # Usage
19
+ my_input = MyInput(root=InputTrigger(protocol="message"))
20
+ print(my_input.root.protocol) # Output: message
21
+ """
22
+
23
+ protocol: str
24
+
25
+
26
+ class InputModel(BaseModel):
27
+ """Base definition of input model showing mandatory root fields.
28
+
29
+ The Model define the Module Input, usually referring to multiple input type defined by an union.
30
+
31
+ Example:
32
+ class ModuleInput(InputModel):
33
+ root: FileInput | MessageInput
34
+ """
35
+
36
+ root: InputTrigger
37
+
38
+
39
+ ConfigSetupModelT = TypeVar("ConfigSetupModelT", bound=BaseModel | None)
40
+ InputModelT = TypeVar("InputModelT", bound=InputModel)
41
+ OutputModelT = TypeVar("OutputModelT", bound=BaseModel)
42
+ SetupModelT = TypeVar("SetupModelT", bound=BaseModel)
43
+ SecretModelT = TypeVar("SecretModelT", bound=BaseModel)
@@ -2,6 +2,6 @@
2
2
 
3
3
  from digitalkin.modules.archetype_module import ArchetypeModule
4
4
  from digitalkin.modules.tool_module import ToolModule
5
- from digitalkin.modules.trigger_module import TriggerModule
5
+ from digitalkin.modules.trigger_handler import TriggerHandler
6
6
 
7
- __all__ = ["ArchetypeModule", "ToolModule", "TriggerModule"]
7
+ __all__ = ["ArchetypeModule", "ToolModule", "TriggerHandler"]
@@ -19,6 +19,8 @@ from digitalkin.models.module import (
19
19
  SecretModelT,
20
20
  SetupModelT,
21
21
  )
22
+ from digitalkin.models.module.module_context import ModuleContext
23
+ from digitalkin.modules.trigger_handler import TriggerHandler
22
24
  from digitalkin.services.agent.agent_strategy import AgentStrategy
23
25
  from digitalkin.services.cost.cost_strategy import CostStrategy
24
26
  from digitalkin.services.filesystem.filesystem_strategy import FilesystemStrategy
@@ -28,17 +30,18 @@ from digitalkin.services.services_config import ServicesConfig, ServicesStrategy
28
30
  from digitalkin.services.snapshot.snapshot_strategy import SnapshotStrategy
29
31
  from digitalkin.services.storage.storage_strategy import StorageStrategy
30
32
  from digitalkin.utils.llm_ready_schema import llm_ready_schema
33
+ from digitalkin.utils.package_discover import ModuleDiscoverer
31
34
 
32
35
 
33
36
  class ModuleErrorModel(BaseModel):
34
- """Typed error/code model."""
37
+ """typed error/code model."""
35
38
 
36
39
  code: str
37
40
  exception: str
38
41
  short_description: str
39
42
 
40
43
 
41
- class BaseModule(
44
+ class BaseModule( # noqa: PLR0904
42
45
  ABC,
43
46
  Generic[
44
47
  InputModelT,
@@ -60,6 +63,9 @@ class BaseModule(
60
63
  secret_format: type[SecretModelT]
61
64
  metadata: ClassVar[dict[str, Any]]
62
65
 
66
+ context: ModuleContext
67
+ triggers_discoverer: ClassVar[ModuleDiscoverer]
68
+
63
69
  # service config params
64
70
  services_config_strategies: ClassVar[dict[str, ServicesStrategy | None]]
65
71
  services_config_params: ClassVar[dict[str, dict[str, Any | None] | None]]
@@ -95,6 +101,13 @@ class BaseModule(
95
101
  # Initialize services configuration
96
102
  self._init_strategies()
97
103
 
104
+ # Initialize minimum context
105
+ self.context = ModuleContext(
106
+ storage=self.storage,
107
+ cost=self.cost,
108
+ filesystem=self.filesystem,
109
+ )
110
+
98
111
  @property
99
112
  def status(self) -> ModuleStatus:
100
113
  """Get the module status.
@@ -165,10 +178,12 @@ class BaseModule(
165
178
  Returns:
166
179
  The JSON schema of the config setup format as a string.
167
180
  """
168
- if cls.config_setup_format is not None:
181
+ config_setup_format = getattr(cls, "config_setup_format", None)
182
+
183
+ if config_setup_format is not None:
169
184
  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)
185
+ return json.dumps(llm_ready_schema(config_setup_format), indent=2)
186
+ return json.dumps(config_setup_format.model_json_schema(), indent=2)
172
187
  msg = "'%s' class does not define an 'config_setup_format'."
173
188
  raise OptionalFeatureNotImplementedError(msg)
174
189
 
@@ -249,6 +264,32 @@ class BaseModule(
249
264
  """
250
265
  return cls.output_format(**output_data)
251
266
 
267
+ @classmethod
268
+ def discover(cls) -> None:
269
+ """Discover and register all TriggerHandler subclasses in the specified package or current directory.
270
+
271
+ Dynamically import all Python modules in the specified package or current directory,
272
+ triggering class registrations for subclasses of TriggerHandler whose names end with 'Trigger'.
273
+
274
+ If a package is provided, all .py files within its path are imported; otherwise, the current
275
+ working directory is searched. For each imported module, any class matching the criteria is
276
+ registered via cls.register(). Errors during import are logged at debug level.
277
+ """
278
+ cls.triggers_discoverer.discover_modules()
279
+ logger.debug("discovered: %s", cls.triggers_discoverer)
280
+
281
+ @classmethod
282
+ def register(cls, handler_cls: type[TriggerHandler]) -> type[TriggerHandler]:
283
+ """Dynamically register the trigger class.
284
+
285
+ Args:
286
+ handler_cls: type of the trigger handler to register.
287
+
288
+ Returns:
289
+ type of the trigger handler.
290
+ """
291
+ return cls.triggers_discoverer.register_trigger(handler_cls)
292
+
252
293
  @abstractmethod
253
294
  async def run_config_setup(
254
295
  self,
@@ -269,15 +310,42 @@ class BaseModule(
269
310
  """Initialize the module."""
270
311
  raise NotImplementedError
271
312
 
272
- @abstractmethod
273
313
  async def run(
274
314
  self,
275
315
  input_data: InputModelT,
276
316
  setup_data: SetupModelT,
277
317
  callback: Callable[[OutputModelT], Coroutine[Any, Any, None]],
278
318
  ) -> None:
279
- """Run the module."""
280
- raise NotImplementedError
319
+ """Run the module with the given input and setup data.
320
+
321
+ This method validates the input data, determines the protocol from the input,
322
+ and dispatches the request to the corresponding trigger handler. The trigger handler
323
+ is responsible for processing the input and invoking the callback with the result.
324
+
325
+ Triggers:
326
+ - The method is triggered when a module run is requested with specific input and setup data.
327
+ - The protocol specified in the input determines which trigger handler is invoked.
328
+
329
+ Args:
330
+ input_data (InputModelT): The input data to be processed by the module.
331
+ setup_data (SetupModelT): The setup or configuration data required for the module.
332
+ callback (Callable[[OutputModelT], Coroutine[Any, Any, None]]): callback to be invoked to stream any result.
333
+
334
+ Raises:
335
+ ValueError: If no handler for the protocol is found.
336
+ """
337
+ input_instance = self.input_format.model_validate(input_data)
338
+ handler_instance = self.triggers_discoverer.get_trigger(
339
+ input_instance.root.protocol,
340
+ input_instance.root,
341
+ )
342
+
343
+ await handler_instance.handle(
344
+ input_instance.root,
345
+ setup_data,
346
+ callback,
347
+ self.context,
348
+ )
281
349
 
282
350
  @abstractmethod
283
351
  async def cleanup(self) -> None:
@@ -338,6 +406,8 @@ class BaseModule(
338
406
  return
339
407
 
340
408
  try:
409
+ logger.info("Init the discod input handlers.")
410
+ self.triggers_discoverer.init_handlers(self.context)
341
411
  logger.info("Run lifecycle")
342
412
  self._status = ModuleStatus.RUNNING
343
413
  self._task = asyncio.create_task(
@@ -0,0 +1,47 @@
1
+ """Definition of the Trigger type."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections.abc import Callable, Coroutine
5
+ from typing import Any, ClassVar, Generic
6
+
7
+ from digitalkin.models.module.module_types import InputModelT, OutputModelT, SetupModelT
8
+ from digitalkin.modules._base_module import ModuleContext
9
+
10
+
11
+ class TriggerHandler(ABC, Generic[InputModelT, SetupModelT, OutputModelT]):
12
+ """Base class for all input-trigger handlers.
13
+
14
+ Each handler declares:
15
+ - protocol_key: the Literal value this handler processes
16
+ - handle(): logic to process the validated payload
17
+ """
18
+
19
+ protocol: ClassVar[str]
20
+ input_format: type[InputModelT]
21
+ output_format: type[OutputModelT]
22
+
23
+ def __init__(self, context: ModuleContext) -> None:
24
+ """Initialize the TriggerHandler with the given context."""
25
+
26
+ @abstractmethod
27
+ async def handle(
28
+ self,
29
+ input_data: InputModelT,
30
+ setup_data: SetupModelT,
31
+ callback: Callable[[Any], Coroutine[Any, Any, None]],
32
+ context: ModuleContext,
33
+ ) -> None:
34
+ """Asynchronously processes the input data specific to Handler and streams results via the provided callback.
35
+
36
+ Args:
37
+ input_data (InputModelT): The input data to be processed by the handler.
38
+ setup_data (SetupModelT): The setup or configuration data required for processing.
39
+ callback (Callable[[Any], Coroutine[Any, Any, None]]): callback that stream results.
40
+ context (ModuleContext): The context object containing module-specific information and resources.
41
+
42
+ Returns:
43
+ Any: The result of the processing, if applicable.
44
+
45
+ Note:
46
+ The callback must be awaited to ensure results are streamed correctly during processing.
47
+ """
@@ -5,7 +5,7 @@ import os
5
5
  import tempfile
6
6
  import uuid
7
7
  from pathlib import Path
8
- from typing import Any
8
+ from typing import Any, Literal
9
9
 
10
10
  from digitalkin.logger import logger
11
11
  from digitalkin.services.filesystem.filesystem_strategy import (
@@ -259,7 +259,17 @@ class DefaultFilesystem(FilesystemStrategy):
259
259
  self,
260
260
  file_id: str,
261
261
  content: bytes | None = None,
262
- file_type: str | None = None,
262
+ file_type: Literal[
263
+ "UNSPECIFIED",
264
+ "DOCUMENT",
265
+ "IMAGE",
266
+ "VIDEO",
267
+ "AUDIO",
268
+ "ARCHIVE",
269
+ "CODE",
270
+ "OTHER",
271
+ ]
272
+ | None = None,
263
273
  content_type: str | None = None,
264
274
  metadata: dict[str, Any] | None = None,
265
275
  new_name: str | None = None,
@@ -2,7 +2,7 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from datetime import datetime
5
- from typing import Any
5
+ from typing import Any, Literal
6
6
 
7
7
  from pydantic import BaseModel, Field
8
8
 
@@ -34,7 +34,21 @@ class FileFilter(BaseModel):
34
34
 
35
35
  names: list[str] | None = Field(default=None, description="Filter by file names (exact matches)")
36
36
  file_ids: list[str] | None = Field(default=None, description="Filter by file IDs")
37
- file_types: list[str] | None = Field(default=None, description="Filter by file types")
37
+ file_types: (
38
+ list[
39
+ Literal[
40
+ "UNSPECIFIED",
41
+ "DOCUMENT",
42
+ "IMAGE",
43
+ "AUDIO",
44
+ "VIDEO",
45
+ "ARCHIVE",
46
+ "CODE",
47
+ "OTHER",
48
+ ]
49
+ ]
50
+ | None
51
+ ) = Field(default=None, description="Filter by file types")
38
52
  created_after: datetime | None = Field(default=None, description="Filter files created after this timestamp")
39
53
  created_before: datetime | None = Field(default=None, description="Filter files created before this timestamp")
40
54
  updated_after: datetime | None = Field(default=None, description="Filter files updated after this timestamp")
@@ -52,7 +66,16 @@ class UploadFileData(BaseModel):
52
66
 
53
67
  content: bytes = Field(description="The content of the file")
54
68
  name: str = Field(description="The name of the file")
55
- file_type: str = Field(description="The type of the file")
69
+ file_type: Literal[
70
+ "UNSPECIFIED",
71
+ "DOCUMENT",
72
+ "IMAGE",
73
+ "AUDIO",
74
+ "VIDEO",
75
+ "ARCHIVE",
76
+ "CODE",
77
+ "OTHER",
78
+ ] = Field(description="The type of the file")
56
79
  content_type: str | None = Field(default=None, description="The content type of the file")
57
80
  metadata: dict[str, Any] | None = Field(default=None, description="The metadata of the file")
58
81
  replace_if_exists: bool = Field(default=False, description="Whether to replace the file if it already exists")
@@ -153,7 +176,17 @@ class FilesystemStrategy(BaseStrategy, ABC):
153
176
  self,
154
177
  file_id: str,
155
178
  content: bytes | None = None,
156
- file_type: str | None = None,
179
+ file_type: Literal[
180
+ "UNSPECIFIED",
181
+ "DOCUMENT",
182
+ "IMAGE",
183
+ "VIDEO",
184
+ "AUDIO",
185
+ "ARCHIVE",
186
+ "CODE",
187
+ "OTHER",
188
+ ]
189
+ | None = None,
157
190
  content_type: str | None = None,
158
191
  metadata: dict[str, Any] | None = None,
159
192
  new_name: str | None = None,
@@ -2,7 +2,7 @@
2
2
 
3
3
  from collections.abc import Generator
4
4
  from contextlib import contextmanager
5
- from typing import Any
5
+ from typing import Any, Literal
6
6
 
7
7
  from digitalkin_proto.digitalkin.filesystem.v1 import filesystem_pb2, filesystem_service_pb2_grpc
8
8
  from google.protobuf import struct_pb2
@@ -103,12 +103,11 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
103
103
  status=self._file_status_to_enum(filters.status) if filters.status else None,
104
104
  )
105
105
 
106
- def _file_proto_to_data(self, file: filesystem_pb2.File, content: bytes | None = None) -> FilesystemRecord:
106
+ def _file_proto_to_data(self, file: filesystem_pb2.File) -> FilesystemRecord:
107
107
  """Convert a File proto message to FilesystemRecord.
108
108
 
109
109
  Args:
110
110
  file: The File proto message to convert
111
- content: The content of the file
112
111
 
113
112
  Returns:
114
113
  FilesystemRecord: The converted data
@@ -124,7 +123,7 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
124
123
  metadata=MessageToDict(file.metadata),
125
124
  storage_url=file.storage_url,
126
125
  status=filesystem_pb2.FileStatus.Name(file.status),
127
- content=content,
126
+ content=file.content,
128
127
  )
129
128
 
130
129
  def __init__(
@@ -213,13 +212,23 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
213
212
 
214
213
  response: filesystem_pb2.GetFileResponse = self.exec_grpc_query("GetFile", request)
215
214
 
216
- return self._file_proto_to_data(response.file, response.content)
215
+ return self._file_proto_to_data(response.file)
217
216
 
218
217
  def update_file(
219
218
  self,
220
219
  file_id: str,
221
220
  content: bytes | None = None,
222
- file_type: str | None = None,
221
+ file_type: Literal[
222
+ "UNSPECIFIED",
223
+ "DOCUMENT",
224
+ "IMAGE",
225
+ "VIDEO",
226
+ "AUDIO",
227
+ "ARCHIVE",
228
+ "CODE",
229
+ "OTHER",
230
+ ]
231
+ | None = None,
223
232
  content_type: str | None = None,
224
233
  metadata: dict[str, Any] | None = None,
225
234
  new_name: str | None = None,