digitalkin 0.2.16__py3-none-any.whl → 0.2.18__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/module_server.py +6 -6
- digitalkin/grpc_servers/module_servicer.py +1 -0
- digitalkin/models/module/__init__.py +15 -1
- digitalkin/models/module/module_context.py +24 -0
- digitalkin/models/module/module_types.py +33 -1
- digitalkin/modules/__init__.py +2 -2
- digitalkin/modules/_base_module.py +78 -8
- digitalkin/modules/trigger_handler.py +47 -0
- digitalkin/services/filesystem/default_filesystem.py +7 -6
- digitalkin/services/filesystem/filesystem_strategy.py +2 -1
- digitalkin/services/filesystem/grpc_filesystem.py +2 -1
- digitalkin/utils/package_discover.py +358 -0
- {digitalkin-0.2.16.dist-info → digitalkin-0.2.18.dist-info}/METADATA +2 -2
- {digitalkin-0.2.16.dist-info → digitalkin-0.2.18.dist-info}/RECORD +18 -16
- digitalkin/modules/trigger_module.py +0 -11
- {digitalkin-0.2.16.dist-info → digitalkin-0.2.18.dist-info}/WHEEL +0 -0
- {digitalkin-0.2.16.dist-info → digitalkin-0.2.18.dist-info}/licenses/LICENSE +0 -0
- {digitalkin-0.2.16.dist-info → digitalkin-0.2.18.dist-info}/top_level.txt +0 -0
digitalkin/__version__.py
CHANGED
|
@@ -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.
|
|
82
|
+
logger.info(self.server_config)
|
|
83
83
|
super().start()
|
|
84
84
|
|
|
85
|
-
logger.
|
|
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.
|
|
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.
|
|
101
|
+
logger.info(self.server_config)
|
|
102
102
|
await super().start_async()
|
|
103
103
|
|
|
104
|
-
logger.
|
|
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.
|
|
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)
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
"""This module contains the models for the modules."""
|
|
2
2
|
|
|
3
3
|
from digitalkin.models.module.module import Module, ModuleStatus
|
|
4
|
+
from digitalkin.models.module.module_context import ModuleContext
|
|
4
5
|
from digitalkin.models.module.module_types import (
|
|
5
6
|
ConfigSetupModelT,
|
|
7
|
+
InputModel,
|
|
6
8
|
InputModelT,
|
|
9
|
+
InputTrigger,
|
|
7
10
|
OutputModelT,
|
|
8
11
|
SecretModelT,
|
|
9
12
|
SetupModelT,
|
|
10
13
|
)
|
|
11
14
|
|
|
12
|
-
__all__ = [
|
|
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
|
|
@@ -4,8 +4,40 @@ from typing import TypeVar
|
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
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
|
+
|
|
7
39
|
ConfigSetupModelT = TypeVar("ConfigSetupModelT", bound=BaseModel | None)
|
|
8
|
-
InputModelT = TypeVar("InputModelT", bound=
|
|
40
|
+
InputModelT = TypeVar("InputModelT", bound=InputModel)
|
|
9
41
|
OutputModelT = TypeVar("OutputModelT", bound=BaseModel)
|
|
10
42
|
SetupModelT = TypeVar("SetupModelT", bound=BaseModel)
|
|
11
43
|
SecretModelT = TypeVar("SecretModelT", bound=BaseModel)
|
digitalkin/modules/__init__.py
CHANGED
|
@@ -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.
|
|
5
|
+
from digitalkin.modules.trigger_handler import TriggerHandler
|
|
6
6
|
|
|
7
|
-
__all__ = ["ArchetypeModule", "ToolModule", "
|
|
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
|
-
"""
|
|
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
|
-
|
|
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(
|
|
171
|
-
return json.dumps(
|
|
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
|
-
|
|
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
|
+
"""
|
|
@@ -126,7 +126,7 @@ class DefaultFilesystem(FilesystemStrategy):
|
|
|
126
126
|
raise FilesystemServiceError(msg) # noqa: TRY301
|
|
127
127
|
|
|
128
128
|
Path(file_path).write_bytes(file.content)
|
|
129
|
-
|
|
129
|
+
storage_uri = str(Path(file_path).resolve())
|
|
130
130
|
file_data = FilesystemRecord(
|
|
131
131
|
id=str(uuid.uuid4()),
|
|
132
132
|
context=self.mission_id,
|
|
@@ -136,7 +136,8 @@ class DefaultFilesystem(FilesystemStrategy):
|
|
|
136
136
|
size_bytes=len(file.content),
|
|
137
137
|
checksum=self._calculate_checksum(file.content),
|
|
138
138
|
metadata=file.metadata,
|
|
139
|
-
|
|
139
|
+
storage_uri=storage_uri,
|
|
140
|
+
file_url=storage_uri,
|
|
140
141
|
status=file.status if hasattr(file, "status") and file.status else "ACTIVE",
|
|
141
142
|
)
|
|
142
143
|
|
|
@@ -200,7 +201,7 @@ class DefaultFilesystem(FilesystemStrategy):
|
|
|
200
201
|
|
|
201
202
|
if include_content:
|
|
202
203
|
for file in paginated_files:
|
|
203
|
-
file.content = Path(file.
|
|
204
|
+
file.content = Path(file.storage_uri).read_bytes()
|
|
204
205
|
|
|
205
206
|
except Exception as e:
|
|
206
207
|
msg = f"Error listing files: {e!s}"
|
|
@@ -243,7 +244,7 @@ class DefaultFilesystem(FilesystemStrategy):
|
|
|
243
244
|
raise FilesystemServiceError(msg) # noqa: TRY301
|
|
244
245
|
|
|
245
246
|
if include_content:
|
|
246
|
-
file_path = file_data.
|
|
247
|
+
file_path = file_data.storage_uri
|
|
247
248
|
if os.path.exists(file_path):
|
|
248
249
|
content = Path(file_path).read_bytes()
|
|
249
250
|
file_data.content = content
|
|
@@ -330,7 +331,7 @@ class DefaultFilesystem(FilesystemStrategy):
|
|
|
330
331
|
new_path = os.path.join(context_dir, new_name)
|
|
331
332
|
os.rename(file_path, new_path)
|
|
332
333
|
existing_file.name = new_name
|
|
333
|
-
existing_file.
|
|
334
|
+
existing_file.storage_uri = str(Path(new_path).resolve())
|
|
334
335
|
|
|
335
336
|
self.db[file_id] = existing_file
|
|
336
337
|
|
|
@@ -388,7 +389,7 @@ class DefaultFilesystem(FilesystemStrategy):
|
|
|
388
389
|
continue
|
|
389
390
|
|
|
390
391
|
try:
|
|
391
|
-
file_path = file_data.
|
|
392
|
+
file_path = file_data.storage_uri
|
|
392
393
|
if os.path.exists(file_path):
|
|
393
394
|
if permanent:
|
|
394
395
|
os.remove(file_path)
|
|
@@ -24,7 +24,8 @@ class FilesystemRecord(BaseModel):
|
|
|
24
24
|
size_bytes: int = Field(default=0, description="Size of the file in bytes")
|
|
25
25
|
checksum: str = Field(default="", description="SHA-256 checksum of the file content")
|
|
26
26
|
metadata: dict[str, Any] | None = Field(default=None, description="Additional metadata for the file")
|
|
27
|
-
|
|
27
|
+
storage_uri: str = Field(description="Internal URI for accessing the file content")
|
|
28
|
+
file_url: str = Field(description="Public URL for accessing the file content")
|
|
28
29
|
status: str = Field(default="UNSPECIFIED", description="Current status of the file")
|
|
29
30
|
content: bytes | None = Field(default=None, description="The content of the file")
|
|
30
31
|
|
|
@@ -121,7 +121,8 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
|
|
|
121
121
|
size_bytes=file.size_bytes,
|
|
122
122
|
checksum=file.checksum,
|
|
123
123
|
metadata=MessageToDict(file.metadata),
|
|
124
|
-
|
|
124
|
+
storage_uri=file.storage_uri,
|
|
125
|
+
file_url=file.file_url,
|
|
125
126
|
status=filesystem_pb2.FileStatus.Name(file.status),
|
|
126
127
|
content=file.content,
|
|
127
128
|
)
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import importlib.util
|
|
5
|
+
import logging
|
|
6
|
+
import pkgutil
|
|
7
|
+
import sys
|
|
8
|
+
from fnmatch import fnmatch
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
|
|
12
|
+
from digitalkin.models.module.module_context import ModuleContext
|
|
13
|
+
from digitalkin.models.module.module_types import InputTrigger
|
|
14
|
+
from digitalkin.modules.trigger_handler import TriggerHandler
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SecurityError(Exception):
|
|
20
|
+
"""Raised when security constraints are violated."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DiscoveryError(Exception):
|
|
24
|
+
"""Raised when discovery fails due to invalid inputs."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ModuleDiscoverer:
|
|
28
|
+
"""Encapsulates secure, structured discovery and import of trigger modules.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
packages: List of Python package paths to scan.
|
|
32
|
+
file_pattern: Glob pattern to match module filenames.
|
|
33
|
+
safe_mode: If True, skips unsafe imports.
|
|
34
|
+
max_file_size: Maximum file size allowed for import (bytes).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
FORBIDDEN_MODULE_PATTERNS: ClassVar[set[str]] = {
|
|
38
|
+
"__pycache__",
|
|
39
|
+
".pyc",
|
|
40
|
+
".pyo",
|
|
41
|
+
".pyd",
|
|
42
|
+
"test_",
|
|
43
|
+
"_test",
|
|
44
|
+
"conftest",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
trigger_handlers: ClassVar[dict[str, tuple[TriggerHandler, ...]]] = {}
|
|
48
|
+
_trigger_handlers_cls: ClassVar[dict[str, list[type[TriggerHandler]]]] = {}
|
|
49
|
+
|
|
50
|
+
def _validate_inputs(self) -> None:
|
|
51
|
+
"""Validate initial discovery inputs.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
DiscoveryError: If packages list is invalid.
|
|
55
|
+
SecurityError: If file pattern or package names are unsafe.
|
|
56
|
+
"""
|
|
57
|
+
if not self.packages or not isinstance(self.packages, list):
|
|
58
|
+
msg = "Packages must be a non-empty list"
|
|
59
|
+
raise DiscoveryError(msg)
|
|
60
|
+
self._validate_file_pattern()
|
|
61
|
+
for pkg in self.packages:
|
|
62
|
+
self._validate_package_name(pkg)
|
|
63
|
+
|
|
64
|
+
def _discover_package(self, package_name: str) -> dict[str, bool]:
|
|
65
|
+
"""Import a package and scan its __path__ for modules.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
package_name: Dotted path of the package to scan.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Mapping of module names to import status.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
pkg = importlib.import_module(package_name)
|
|
75
|
+
except ImportError:
|
|
76
|
+
logger.exception("Could not import package %s", package_name)
|
|
77
|
+
return {}
|
|
78
|
+
|
|
79
|
+
paths = getattr(pkg, "__path__", None)
|
|
80
|
+
if not paths:
|
|
81
|
+
logger.warning("Package %s has no __path__", package_name)
|
|
82
|
+
return {}
|
|
83
|
+
|
|
84
|
+
results: dict[str, bool] = {}
|
|
85
|
+
for path_str in paths:
|
|
86
|
+
base_path = Path(path_str).resolve()
|
|
87
|
+
results.update(self._discover_in_path(package_name, base_path))
|
|
88
|
+
return results
|
|
89
|
+
|
|
90
|
+
def _discover_in_path(self, package_name: str, base_path: Path) -> dict[str, bool]:
|
|
91
|
+
"""Walk a filesystem path to locate and process modules.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
package_name: Root package name for prefixing.
|
|
95
|
+
base_path: Filesystem path of the package.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Mapping of module names to import status.
|
|
99
|
+
"""
|
|
100
|
+
results: dict[str, bool] = {}
|
|
101
|
+
if not base_path.is_dir():
|
|
102
|
+
logger.warning("Invalid package path: %s", base_path)
|
|
103
|
+
return results
|
|
104
|
+
|
|
105
|
+
for _, module_name, is_pkg in pkgutil.walk_packages(
|
|
106
|
+
[str(base_path)], prefix=f"{package_name}.", onerror=lambda e: logger.error("Walk error: %s", e)
|
|
107
|
+
):
|
|
108
|
+
if is_pkg or module_name in results:
|
|
109
|
+
continue
|
|
110
|
+
results[module_name] = self._process_module(module_name, base_path, package_name)
|
|
111
|
+
return results
|
|
112
|
+
|
|
113
|
+
def _process_module(self, module_name: str, base_path: Path, package_name: str) -> bool:
|
|
114
|
+
"""Validate module file, import it, and validate the trigger class.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
module_name: Full dotted module path.
|
|
118
|
+
base_path: Filesystem base path of the package.
|
|
119
|
+
package_name: Root package for path resolution.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
True if import and validation succeed, False otherwise.
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
module_file = self._module_file_path(module_name, base_path, package_name)
|
|
126
|
+
self._validate_module_path(module_file, base_path)
|
|
127
|
+
if not fnmatch(module_file.name, self.file_pattern):
|
|
128
|
+
return False
|
|
129
|
+
if not self._is_safe_module_name(module_name):
|
|
130
|
+
logger.debug("Skipping unsafe module: %s", module_name)
|
|
131
|
+
return False
|
|
132
|
+
if not self._safe_import_module(module_name, module_file):
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
except SecurityError:
|
|
136
|
+
logger.exception("Security violation %s", module_name)
|
|
137
|
+
return False
|
|
138
|
+
except Exception:
|
|
139
|
+
logger.exception("Error processing %s", module_name)
|
|
140
|
+
return False
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _module_file_path(module_name: str, base_path: Path, package_name: str) -> Path:
|
|
145
|
+
"""Compute filesystem Path for a module's .py file.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
module_name: Full module name.
|
|
149
|
+
base_path: Base directory of the package.
|
|
150
|
+
package_name: Root package prefix.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Path to the module's .py file.
|
|
154
|
+
"""
|
|
155
|
+
rel = module_name.replace(f"{package_name}.", "").replace(".", "/")
|
|
156
|
+
return base_path / f"{rel}.py"
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _validate_package_name(package_name: str) -> None:
|
|
160
|
+
"""Validate that a package name is safe and well-formed.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
package_name: Dotted Python package name.
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
SecurityError: On invalid package names.
|
|
167
|
+
"""
|
|
168
|
+
if not package_name or not isinstance(package_name, str):
|
|
169
|
+
msg = "Package name must be a non-empty string"
|
|
170
|
+
raise SecurityError(msg)
|
|
171
|
+
if any(part in package_name for part in ("..", "/", "\\", "\x00")):
|
|
172
|
+
msg = "Invalid package name: %s"
|
|
173
|
+
raise SecurityError(msg, package_name)
|
|
174
|
+
if not all(part.isidentifier() for part in package_name.split(".")):
|
|
175
|
+
msg = "Invalid Python package name: %s"
|
|
176
|
+
raise SecurityError(msg, package_name)
|
|
177
|
+
|
|
178
|
+
def _validate_file_pattern(self) -> None:
|
|
179
|
+
"""Validate that the file glob pattern is safe.
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
SecurityError: On dangerous patterns.
|
|
183
|
+
"""
|
|
184
|
+
pattern = self.file_pattern
|
|
185
|
+
if not pattern or not isinstance(pattern, str):
|
|
186
|
+
msg = "File pattern must be a non-empty string"
|
|
187
|
+
raise SecurityError(msg)
|
|
188
|
+
if any(d in pattern for d in ("..", "/", "\\", "\x00", "**/")):
|
|
189
|
+
msg = "Dangerous pattern detected: %s"
|
|
190
|
+
raise SecurityError(msg, pattern)
|
|
191
|
+
if not pattern.endswith(".py"):
|
|
192
|
+
msg = "Pattern must target Python files (.py)"
|
|
193
|
+
raise SecurityError(msg)
|
|
194
|
+
|
|
195
|
+
def _validate_module_path(self, module_path: Path, base_path: Path) -> None:
|
|
196
|
+
"""Ensure module_path resides under base_path and is within size limits.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
module_path: Path to the module file.
|
|
200
|
+
base_path: Root directory for the package.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
SecurityError: On invalid paths or oversize files.
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
resolved_module = module_path.resolve()
|
|
207
|
+
resolved_base = base_path.resolve()
|
|
208
|
+
if not str(resolved_module).startswith(str(resolved_base)):
|
|
209
|
+
msg = "Path traversal attempt: %s"
|
|
210
|
+
raise SecurityError(msg, module_path)
|
|
211
|
+
if not resolved_module.exists() or not resolved_module.is_file():
|
|
212
|
+
msg = "Invalid module path: %s"
|
|
213
|
+
raise SecurityError(msg, module_path)
|
|
214
|
+
if resolved_module.stat().st_size > self.max_file_size:
|
|
215
|
+
msg = "Module file too large: %s"
|
|
216
|
+
raise SecurityError(msg, module_path)
|
|
217
|
+
except (OSError, ValueError) as e:
|
|
218
|
+
msg = "Invalid module path: %s"
|
|
219
|
+
raise SecurityError(msg, module_path) from e
|
|
220
|
+
|
|
221
|
+
def _is_safe_module_name(self, module_name: str) -> bool:
|
|
222
|
+
"""Check module name against forbidden patterns.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
module_name: Full dotted module name.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
True if safe, False otherwise.
|
|
229
|
+
"""
|
|
230
|
+
if not module_name or not all(part.isidentifier() for part in module_name.split(".")):
|
|
231
|
+
return False
|
|
232
|
+
return not any(p in module_name for p in self.FORBIDDEN_MODULE_PATTERNS)
|
|
233
|
+
|
|
234
|
+
def _safe_import_module(self, module_name: str, module_path: Path) -> bool:
|
|
235
|
+
"""Import a module by spec and execute it.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
module_name: Dotted module name.
|
|
239
|
+
module_path: Filesystem path to .py file.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
True if imported successfully, False otherwise.
|
|
243
|
+
"""
|
|
244
|
+
try:
|
|
245
|
+
if not self._is_safe_module_name(module_name):
|
|
246
|
+
return False
|
|
247
|
+
if module_name in sys.modules:
|
|
248
|
+
logger.debug("Module %s already imported", module_name)
|
|
249
|
+
return True
|
|
250
|
+
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
|
251
|
+
if spec is None or spec.loader is None:
|
|
252
|
+
logger.error("Could not create valid spec for %s", module_name)
|
|
253
|
+
return False
|
|
254
|
+
module = importlib.util.module_from_spec(spec)
|
|
255
|
+
sys.modules[module_name] = module
|
|
256
|
+
spec.loader.exec_module(module)
|
|
257
|
+
logger.debug("Successfully imported %s", module_name)
|
|
258
|
+
except Exception:
|
|
259
|
+
sys.modules.pop(module_name, None)
|
|
260
|
+
logger.exception("Failed to import %s", module_name)
|
|
261
|
+
return False
|
|
262
|
+
return True
|
|
263
|
+
|
|
264
|
+
def __str__(self) -> str:
|
|
265
|
+
"""Return a string representation of registered trigger handler classes."""
|
|
266
|
+
return str(self._trigger_handlers_cls)
|
|
267
|
+
|
|
268
|
+
def __init__(
|
|
269
|
+
self,
|
|
270
|
+
packages: list[str],
|
|
271
|
+
file_pattern: str = "*_trigger.py",
|
|
272
|
+
max_file_size: int = 1024 * 1024, # 1Mb
|
|
273
|
+
) -> None:
|
|
274
|
+
"""Initialize the discoverer.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
packages: List of package names to scan.
|
|
278
|
+
file_pattern: Glob pattern for matching modules.
|
|
279
|
+
safe_mode: If True, blocks modules with forbidden names.
|
|
280
|
+
max_file_size: Limit for module file sizes in bytes.
|
|
281
|
+
"""
|
|
282
|
+
self.packages = packages
|
|
283
|
+
self.file_pattern = file_pattern
|
|
284
|
+
self.max_file_size = max_file_size
|
|
285
|
+
|
|
286
|
+
def discover_modules(self) -> dict[str, bool]:
|
|
287
|
+
"""Discover and import matching modules across configured packages.
|
|
288
|
+
|
|
289
|
+
Raises:
|
|
290
|
+
DiscoveryError: If initial inputs are invalid.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
results infos
|
|
294
|
+
"""
|
|
295
|
+
self._validate_inputs()
|
|
296
|
+
results: dict[str, bool] = {}
|
|
297
|
+
for pkg in self.packages:
|
|
298
|
+
results.update(self._discover_package(pkg))
|
|
299
|
+
return results
|
|
300
|
+
|
|
301
|
+
def register_trigger(self, handler_cls: type[TriggerHandler]) -> type[TriggerHandler]:
|
|
302
|
+
"""Register a trigger handler class for a specific protocol.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
handler_cls: The trigger handler class to register.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
The registered trigger handler class.
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
ValueError: If a handler for the protocol is already registered.
|
|
312
|
+
"""
|
|
313
|
+
key = handler_cls.protocol
|
|
314
|
+
if key not in self._trigger_handlers_cls:
|
|
315
|
+
self._trigger_handlers_cls[key] = []
|
|
316
|
+
|
|
317
|
+
self._trigger_handlers_cls[key].append(handler_cls)
|
|
318
|
+
return handler_cls
|
|
319
|
+
|
|
320
|
+
def init_handlers(self, context: ModuleContext) -> None:
|
|
321
|
+
"""Initialize all registered trigger handler instances.
|
|
322
|
+
|
|
323
|
+
This method iterates over all registered trigger handler classes in
|
|
324
|
+
`_trigger_handlers_cls`, instantiates each handler with the current module
|
|
325
|
+
context, and stores the instance in `_trigger_handlers`.
|
|
326
|
+
This allows the module to dispatch incoming protocol requests
|
|
327
|
+
to the correct handler instance at runtime while keeping a shared context.
|
|
328
|
+
"""
|
|
329
|
+
for protocol, handlers_cls in self._trigger_handlers_cls.items():
|
|
330
|
+
self.trigger_handlers[protocol] = tuple(handler_cls(context) for handler_cls in set(handlers_cls))
|
|
331
|
+
|
|
332
|
+
def get_trigger(self, protocol: str, input_instance: InputTrigger) -> TriggerHandler:
|
|
333
|
+
"""Retrieve a trigger handler instance based on the provided protocol and input instance type.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
protocol: The protocol name (ignored internally, `input_instance.protocol` is used instead).
|
|
337
|
+
input_instance: The input trigger instance used to determine the correct handler.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
TriggerHandler: An instance of the trigger handler matching the input format.
|
|
341
|
+
|
|
342
|
+
Raises:
|
|
343
|
+
ValueError: If no handler is registered for the specified protocol,
|
|
344
|
+
or if no handler matches the type of the input instance.
|
|
345
|
+
"""
|
|
346
|
+
logger.debug("Trigger type invoked: %s", input_instance)
|
|
347
|
+
protocol = input_instance.protocol
|
|
348
|
+
|
|
349
|
+
if (protocols := self.trigger_handlers.get(protocol)) is None:
|
|
350
|
+
msg = f"No handler for protocol '{protocol}'"
|
|
351
|
+
raise ValueError(msg)
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
handler_instance = next(x for x in protocols if isinstance(input_instance, x.input_format))
|
|
355
|
+
except Exception:
|
|
356
|
+
msg = f"No handler for input format '{type(input_instance)=}'"
|
|
357
|
+
raise ValueError(msg)
|
|
358
|
+
return handler_instance
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: digitalkin
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.18
|
|
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
|
|
@@ -452,7 +452,7 @@ Classifier: License :: Other/Proprietary License
|
|
|
452
452
|
Requires-Python: >=3.10
|
|
453
453
|
Description-Content-Type: text/markdown
|
|
454
454
|
License-File: LICENSE
|
|
455
|
-
Requires-Dist: digitalkin-proto>=0.1.
|
|
455
|
+
Requires-Dist: digitalkin-proto>=0.1.16
|
|
456
456
|
Requires-Dist: grpcio-health-checking>=1.71.0
|
|
457
457
|
Requires-Dist: grpcio-reflection>=1.71.0
|
|
458
458
|
Requires-Dist: grpcio-status>=1.71.0
|
|
@@ -7,13 +7,13 @@ base_server/mock/__init__.py,sha256=YZFT-F1l_TpvJYuIPX-7kTeE1CfOjhx9YmNRXVoi-jQ,
|
|
|
7
7
|
base_server/mock/mock_pb2.py,sha256=sETakcS3PAAm4E-hTCV1jIVaQTPEAIoVVHupB8Z_k7Y,1843
|
|
8
8
|
base_server/mock/mock_pb2_grpc.py,sha256=BbOT70H6q3laKgkHfOx1QdfmCS_HxCY4wCOX84YAdG4,3180
|
|
9
9
|
digitalkin/__init__.py,sha256=7LLBAba0th-3SGqcpqFO-lopWdUkVLKzLZiMtB-mW3M,162
|
|
10
|
-
digitalkin/__version__.py,sha256=
|
|
10
|
+
digitalkin/__version__.py,sha256=Xmsz_szPPAmeYnVobFHJO8pKINhzpB446tyRoVs8I2Y,191
|
|
11
11
|
digitalkin/logger.py,sha256=cFbIAZHOFx3nddOssRNYLXyqUPzR4CgDR_c-5wmB-og,1685
|
|
12
12
|
digitalkin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
digitalkin/grpc_servers/__init__.py,sha256=0cJBlwipSmFdXkyH3T0i6OJ1WpAtNsZgYX7JaSnkbtg,804
|
|
14
14
|
digitalkin/grpc_servers/_base_server.py,sha256=NXnnZPjJqUDWoumhEbb7EOEWB7d8XYwpQs-l97NTe4k,18647
|
|
15
|
-
digitalkin/grpc_servers/module_server.py,sha256=
|
|
16
|
-
digitalkin/grpc_servers/module_servicer.py,sha256=
|
|
15
|
+
digitalkin/grpc_servers/module_server.py,sha256=hOvoY2XFjxmgkbAsMex5a-m7OPyljnz0Gh9BJxtDtJo,10259
|
|
16
|
+
digitalkin/grpc_servers/module_servicer.py,sha256=yHlzMBBdjdEirx6otG0u3fNy-nZgFkK2HAWQJ6LA7jQ,18415
|
|
17
17
|
digitalkin/grpc_servers/registry_server.py,sha256=StY18DKYoPKQIU1SIzgito6D4_QA1aMVddZ8O2WGlHY,2223
|
|
18
18
|
digitalkin/grpc_servers/registry_servicer.py,sha256=dqsKGHZ0LnaIvGt4ipaAuigd37sbJBndT4MAT029GsY,16471
|
|
19
19
|
digitalkin/grpc_servers/utils/exceptions.py,sha256=SyOgvjggaUECYmSiqy8KJLHwHVt5IClSTxslHM-IZzI,931
|
|
@@ -22,17 +22,18 @@ digitalkin/grpc_servers/utils/grpc_client_wrapper.py,sha256=GXRqCTCufwWd8sVMoCln
|
|
|
22
22
|
digitalkin/grpc_servers/utils/models.py,sha256=80F5oHiv8MOqMoDZJBXmJSRoVYJRyhaVcijQ2LinAig,8428
|
|
23
23
|
digitalkin/grpc_servers/utils/types.py,sha256=rQ78s4nAet2jy-NIDj_PUWriT0kuGHr_w6ELjmjgBao,539
|
|
24
24
|
digitalkin/models/__init__.py,sha256=hDHtUfswaNh8wo4NZaBItg9JqC0uNSRqXArNWSrGynY,163
|
|
25
|
-
digitalkin/models/module/__init__.py,sha256=
|
|
25
|
+
digitalkin/models/module/__init__.py,sha256=CSnXBcuE8omdbQllfPQGMHaWsfiN7FJyigEv1eZeO4M,579
|
|
26
26
|
digitalkin/models/module/module.py,sha256=NWOwCeD_NH0NL89AF_NJ0SwE7G1n-HTcRalodLCDmpM,749
|
|
27
|
-
digitalkin/models/module/
|
|
27
|
+
digitalkin/models/module/module_context.py,sha256=AL6bzwjYfYPLHb1Hu5kKBjaIOxCpBCPMvxEnapdyrX4,1003
|
|
28
|
+
digitalkin/models/module/module_types.py,sha256=HWp-CRG0AB7ve9CRqOegbcwqDgXZ0hmS-QxDpgbdMmI,1196
|
|
28
29
|
digitalkin/models/services/__init__.py,sha256=HsW7MUGFPvH7Ri28WN4BHHBfEQk5dzU_9FOWAc-0lSE,56
|
|
29
30
|
digitalkin/models/services/cost.py,sha256=QTEuFD6xz62nob0z4ksE-INJWcZ-iFiuNW5mvXhpFes,1599
|
|
30
31
|
digitalkin/models/services/storage.py,sha256=cYTVIriGKiprF9OerhSxmc_jM6fUTVwmeon1yQCinkE,143
|
|
31
|
-
digitalkin/modules/__init__.py,sha256=
|
|
32
|
-
digitalkin/modules/_base_module.py,sha256=
|
|
32
|
+
digitalkin/modules/__init__.py,sha256=VwVbKok81NGyPIBZgEj_SR-59G8tTlSb4eBJI9W6Vx4,281
|
|
33
|
+
digitalkin/modules/_base_module.py,sha256=tYFWMB09jO0VLOYA3pUSHeI7ujdwnsIqmgPDJE2WtSU,15981
|
|
33
34
|
digitalkin/modules/archetype_module.py,sha256=7rleVOkZfFR7TQeaq6oZXLBuRWixKsUJMOr9HfDV0T8,566
|
|
34
35
|
digitalkin/modules/tool_module.py,sha256=7eWem70dEJiyoCtkGIJP4Ak2G8MzSruOjrByQMqW9-I,508
|
|
35
|
-
digitalkin/modules/
|
|
36
|
+
digitalkin/modules/trigger_handler.py,sha256=TpxFc_xkYia9B9rAvHkj02e818W08JOfIqssQqbsCpM,1785
|
|
36
37
|
digitalkin/modules/job_manager/base_job_manager.py,sha256=Ku_MT7Cuf1R_qJU7SV-yHkzUhhMtmDOa8t5DyetvMxw,6177
|
|
37
38
|
digitalkin/modules/job_manager/job_manager_models.py,sha256=onHy-DfInLZQveniMIWIKwTKSQjojz500JvHB54x93c,1129
|
|
38
39
|
digitalkin/modules/job_manager/single_job_manager.py,sha256=DkQV3-E9XqgmuC6hoYKolTX2iv9lUJ8BSCde3zk1OU8,10264
|
|
@@ -50,9 +51,9 @@ digitalkin/services/cost/cost_strategy.py,sha256=VhHeqi9WnE1yoDBBVp5qmqwIt5tTZHU
|
|
|
50
51
|
digitalkin/services/cost/default_cost.py,sha256=mEd0VL_tMcGU41q0f9MFeBYeKZBenv0mIHuwgXlQ7uQ,3869
|
|
51
52
|
digitalkin/services/cost/grpc_cost.py,sha256=p_5mG72N7e4bxwBOD9DNokvLtinBILiqCfllmkqpmhw,6253
|
|
52
53
|
digitalkin/services/filesystem/__init__.py,sha256=BhwMl_BUvM0d65fmglkp0SVwn3RfYiUOKJgIMnOCaGM,381
|
|
53
|
-
digitalkin/services/filesystem/default_filesystem.py,sha256=
|
|
54
|
-
digitalkin/services/filesystem/filesystem_strategy.py,sha256
|
|
55
|
-
digitalkin/services/filesystem/grpc_filesystem.py,sha256=
|
|
54
|
+
digitalkin/services/filesystem/default_filesystem.py,sha256=pQI7Sc9WNJrxxwd21iIviWcrKKuxuj3BdoAT-rghYGk,15023
|
|
55
|
+
digitalkin/services/filesystem/filesystem_strategy.py,sha256=MlSgEDjjCy1GjtpdO_rKiF2NtfP4DpSU_VF2UtwQ7Ug,9130
|
|
56
|
+
digitalkin/services/filesystem/grpc_filesystem.py,sha256=W-TYe76Zbf2KS0Z4NMVUjRdP6m2IzU7-VXiBdecOjpk,12489
|
|
56
57
|
digitalkin/services/identity/__init__.py,sha256=InkeyLgFYYwItx8mePA8HpfacOMWZwwuc0G4pWtKq9s,270
|
|
57
58
|
digitalkin/services/identity/default_identity.py,sha256=Y2auZHrGSZTIN5D8HyjLvLcNbYFM1CNUE23x7p5VIGw,386
|
|
58
59
|
digitalkin/services/identity/identity_strategy.py,sha256=skappBbds1_qa0Gr24FGrNX1N0_OYhYT1Lh7dUaAirE,429
|
|
@@ -74,14 +75,15 @@ digitalkin/utils/__init__.py,sha256=sJnY-ZUgsjMfojAjONC1VN14mhgIDnzyOlGkw21rRnM,
|
|
|
74
75
|
digitalkin/utils/arg_parser.py,sha256=nvjI1pKDY1HfS0oGcMQPtdTQcggXLtpxXMbnMxNEKRU,3109
|
|
75
76
|
digitalkin/utils/development_mode_action.py,sha256=TqRuAF_A7bDD4twRB4PnZcRoNeaiAnEdxM5kvy4aoaA,1511
|
|
76
77
|
digitalkin/utils/llm_ready_schema.py,sha256=JjMug_lrQllqFoanaC091VgOqwAd-_YzcpqFlS7p778,2375
|
|
77
|
-
digitalkin
|
|
78
|
+
digitalkin/utils/package_discover.py,sha256=aASTSloPVTp6MAaj5T7ToIeC6NHKajPDjWa1LoGjj3g,13473
|
|
79
|
+
digitalkin-0.2.18.dist-info/licenses/LICENSE,sha256=Ies4HFv2r2hzDRakJYxk3Y60uDFLiG-orIgeTpstnIo,20327
|
|
78
80
|
modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
81
|
modules/cpu_intensive_module.py,sha256=ejB9XPnFfA0uCuFUQbM3fy5UYfqqAlF36rv_P5Ri8ho,8363
|
|
80
82
|
modules/minimal_llm_module.py,sha256=Ijld__ZnhzfLwpXD1XVkLZ7jyKZKyOFZczOpiPttJZc,11216
|
|
81
83
|
modules/text_transform_module.py,sha256=bwPSnEUthZQyfLwcTLo52iAxItAoknkLh8Y3m5aywaY,7251
|
|
82
84
|
services/filesystem_module.py,sha256=71Mcja8jCQqiqFHPdsIXplFIHTvgkxRhp0TRXuCfgkk,7430
|
|
83
85
|
services/storage_module.py,sha256=ybTMqmvGaTrR8PqJ4FU0cwxaDjT36TskVrGoetTGmno,6955
|
|
84
|
-
digitalkin-0.2.
|
|
85
|
-
digitalkin-0.2.
|
|
86
|
-
digitalkin-0.2.
|
|
87
|
-
digitalkin-0.2.
|
|
86
|
+
digitalkin-0.2.18.dist-info/METADATA,sha256=C29QQiRskIqGJUn4NjC_237SGkhAC1j0wEYUdaLzXEY,30580
|
|
87
|
+
digitalkin-0.2.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
88
|
+
digitalkin-0.2.18.dist-info/top_level.txt,sha256=gcjqlyrZuLjIyxrOIavCQM_olpr6ND5kPKkZd2j0xGo,40
|
|
89
|
+
digitalkin-0.2.18.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"""TriggerModule extends BaseModule to implement specific module types."""
|
|
2
|
-
|
|
3
|
-
from abc import ABC
|
|
4
|
-
|
|
5
|
-
from digitalkin.models.module.module_types import ConfigSetupModelT
|
|
6
|
-
from digitalkin.modules._base_module import BaseModule, InputModelT, OutputModelT, SecretModelT, SetupModelT
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TriggerModule(BaseModule[InputModelT, OutputModelT, SetupModelT, SecretModelT,
|
|
10
|
-
ConfigSetupModelT,], ABC):
|
|
11
|
-
"""TriggerModule extends BaseModule to implement specific module types."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|