digitalkin 0.3.2.dev2__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.
- base_server/__init__.py +1 -0
- base_server/mock/__init__.py +5 -0
- base_server/mock/mock_pb2.py +39 -0
- base_server/mock/mock_pb2_grpc.py +102 -0
- base_server/server_async_insecure.py +125 -0
- base_server/server_async_secure.py +143 -0
- base_server/server_sync_insecure.py +103 -0
- base_server/server_sync_secure.py +122 -0
- digitalkin/__init__.py +8 -0
- digitalkin/__version__.py +8 -0
- digitalkin/core/__init__.py +1 -0
- digitalkin/core/common/__init__.py +9 -0
- digitalkin/core/common/factories.py +156 -0
- digitalkin/core/job_manager/__init__.py +1 -0
- digitalkin/core/job_manager/base_job_manager.py +288 -0
- digitalkin/core/job_manager/single_job_manager.py +354 -0
- digitalkin/core/job_manager/taskiq_broker.py +311 -0
- digitalkin/core/job_manager/taskiq_job_manager.py +541 -0
- digitalkin/core/task_manager/__init__.py +1 -0
- digitalkin/core/task_manager/base_task_manager.py +539 -0
- digitalkin/core/task_manager/local_task_manager.py +108 -0
- digitalkin/core/task_manager/remote_task_manager.py +87 -0
- digitalkin/core/task_manager/surrealdb_repository.py +266 -0
- digitalkin/core/task_manager/task_executor.py +249 -0
- digitalkin/core/task_manager/task_session.py +406 -0
- digitalkin/grpc_servers/__init__.py +1 -0
- digitalkin/grpc_servers/_base_server.py +486 -0
- digitalkin/grpc_servers/module_server.py +208 -0
- digitalkin/grpc_servers/module_servicer.py +516 -0
- digitalkin/grpc_servers/utils/__init__.py +1 -0
- digitalkin/grpc_servers/utils/exceptions.py +29 -0
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +88 -0
- digitalkin/grpc_servers/utils/grpc_error_handler.py +53 -0
- digitalkin/grpc_servers/utils/utility_schema_extender.py +97 -0
- digitalkin/logger.py +157 -0
- digitalkin/mixins/__init__.py +19 -0
- digitalkin/mixins/base_mixin.py +10 -0
- digitalkin/mixins/callback_mixin.py +24 -0
- digitalkin/mixins/chat_history_mixin.py +110 -0
- digitalkin/mixins/cost_mixin.py +76 -0
- digitalkin/mixins/file_history_mixin.py +93 -0
- digitalkin/mixins/filesystem_mixin.py +46 -0
- digitalkin/mixins/logger_mixin.py +51 -0
- digitalkin/mixins/storage_mixin.py +79 -0
- digitalkin/models/__init__.py +8 -0
- digitalkin/models/core/__init__.py +1 -0
- digitalkin/models/core/job_manager_models.py +36 -0
- digitalkin/models/core/task_monitor.py +70 -0
- digitalkin/models/grpc_servers/__init__.py +1 -0
- digitalkin/models/grpc_servers/models.py +275 -0
- digitalkin/models/grpc_servers/types.py +24 -0
- digitalkin/models/module/__init__.py +25 -0
- digitalkin/models/module/module.py +40 -0
- digitalkin/models/module/module_context.py +149 -0
- digitalkin/models/module/module_types.py +393 -0
- digitalkin/models/module/utility.py +146 -0
- digitalkin/models/services/__init__.py +10 -0
- digitalkin/models/services/cost.py +54 -0
- digitalkin/models/services/registry.py +42 -0
- digitalkin/models/services/storage.py +44 -0
- digitalkin/modules/__init__.py +11 -0
- digitalkin/modules/_base_module.py +517 -0
- digitalkin/modules/archetype_module.py +23 -0
- digitalkin/modules/tool_module.py +23 -0
- digitalkin/modules/trigger_handler.py +48 -0
- digitalkin/modules/triggers/__init__.py +12 -0
- digitalkin/modules/triggers/healthcheck_ping_trigger.py +45 -0
- digitalkin/modules/triggers/healthcheck_services_trigger.py +63 -0
- digitalkin/modules/triggers/healthcheck_status_trigger.py +52 -0
- digitalkin/py.typed +0 -0
- digitalkin/services/__init__.py +30 -0
- digitalkin/services/agent/__init__.py +6 -0
- digitalkin/services/agent/agent_strategy.py +19 -0
- digitalkin/services/agent/default_agent.py +13 -0
- digitalkin/services/base_strategy.py +22 -0
- digitalkin/services/communication/__init__.py +7 -0
- digitalkin/services/communication/communication_strategy.py +76 -0
- digitalkin/services/communication/default_communication.py +101 -0
- digitalkin/services/communication/grpc_communication.py +223 -0
- digitalkin/services/cost/__init__.py +14 -0
- digitalkin/services/cost/cost_strategy.py +100 -0
- digitalkin/services/cost/default_cost.py +114 -0
- digitalkin/services/cost/grpc_cost.py +138 -0
- digitalkin/services/filesystem/__init__.py +7 -0
- digitalkin/services/filesystem/default_filesystem.py +417 -0
- digitalkin/services/filesystem/filesystem_strategy.py +252 -0
- digitalkin/services/filesystem/grpc_filesystem.py +317 -0
- digitalkin/services/identity/__init__.py +6 -0
- digitalkin/services/identity/default_identity.py +15 -0
- digitalkin/services/identity/identity_strategy.py +14 -0
- digitalkin/services/registry/__init__.py +27 -0
- digitalkin/services/registry/default_registry.py +141 -0
- digitalkin/services/registry/exceptions.py +47 -0
- digitalkin/services/registry/grpc_registry.py +306 -0
- digitalkin/services/registry/registry_models.py +43 -0
- digitalkin/services/registry/registry_strategy.py +98 -0
- digitalkin/services/services_config.py +200 -0
- digitalkin/services/services_models.py +65 -0
- digitalkin/services/setup/__init__.py +1 -0
- digitalkin/services/setup/default_setup.py +219 -0
- digitalkin/services/setup/grpc_setup.py +343 -0
- digitalkin/services/setup/setup_strategy.py +145 -0
- digitalkin/services/snapshot/__init__.py +6 -0
- digitalkin/services/snapshot/default_snapshot.py +39 -0
- digitalkin/services/snapshot/snapshot_strategy.py +30 -0
- digitalkin/services/storage/__init__.py +7 -0
- digitalkin/services/storage/default_storage.py +228 -0
- digitalkin/services/storage/grpc_storage.py +214 -0
- digitalkin/services/storage/storage_strategy.py +273 -0
- digitalkin/services/user_profile/__init__.py +12 -0
- digitalkin/services/user_profile/default_user_profile.py +55 -0
- digitalkin/services/user_profile/grpc_user_profile.py +69 -0
- digitalkin/services/user_profile/user_profile_strategy.py +40 -0
- digitalkin/utils/__init__.py +29 -0
- digitalkin/utils/arg_parser.py +92 -0
- digitalkin/utils/development_mode_action.py +51 -0
- digitalkin/utils/dynamic_schema.py +483 -0
- digitalkin/utils/llm_ready_schema.py +75 -0
- digitalkin/utils/package_discover.py +357 -0
- digitalkin-0.3.2.dev2.dist-info/METADATA +602 -0
- digitalkin-0.3.2.dev2.dist-info/RECORD +131 -0
- digitalkin-0.3.2.dev2.dist-info/WHEEL +5 -0
- digitalkin-0.3.2.dev2.dist-info/licenses/LICENSE +430 -0
- digitalkin-0.3.2.dev2.dist-info/top_level.txt +4 -0
- modules/__init__.py +0 -0
- modules/cpu_intensive_module.py +280 -0
- modules/dynamic_setup_module.py +338 -0
- modules/minimal_llm_module.py +347 -0
- modules/text_transform_module.py +203 -0
- services/filesystem_module.py +200 -0
- services/storage_module.py +206 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Storage Mixin to ease storage access in Triggers."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from digitalkin.models.module.module_context import ModuleContext
|
|
6
|
+
from digitalkin.services.storage.storage_strategy import StorageRecord
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StorageMixin:
|
|
10
|
+
"""Mixin providing storage operations through the storage strategy.
|
|
11
|
+
|
|
12
|
+
This mixin wraps storage strategy calls to provide a cleaner API
|
|
13
|
+
for trigger handlers.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def store_storage(
|
|
18
|
+
context: ModuleContext,
|
|
19
|
+
collection: str,
|
|
20
|
+
record_id: str | None,
|
|
21
|
+
data: dict[str, Any],
|
|
22
|
+
data_type: Literal["OUTPUT", "VIEW", "LOGS", "OTHER"] = "OUTPUT",
|
|
23
|
+
) -> StorageRecord:
|
|
24
|
+
"""Store data using the storage strategy.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
context: Module context containing the storage strategy
|
|
28
|
+
collection: Collection name for the data
|
|
29
|
+
record_id: Optional record identifier
|
|
30
|
+
data: Data to store
|
|
31
|
+
data_type: Type of data being stored
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Result from the storage strategy
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
StorageServiceError: If storage operation fails
|
|
38
|
+
"""
|
|
39
|
+
return context.storage.store(collection, record_id, data, data_type=data_type)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def read_storage(context: ModuleContext, collection: str, record_id: str) -> StorageRecord | None:
|
|
43
|
+
"""Read data from storage.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
context: Module context containing the storage strategy
|
|
47
|
+
collection: Collection name
|
|
48
|
+
record_id: Record identifier
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Retrieved data
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
StorageServiceError: If read operation fails
|
|
55
|
+
"""
|
|
56
|
+
return context.storage.read(collection, record_id)
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def update_storage(
|
|
60
|
+
context: ModuleContext,
|
|
61
|
+
collection: str,
|
|
62
|
+
record_id: str,
|
|
63
|
+
data: dict[str, Any],
|
|
64
|
+
) -> StorageRecord | None:
|
|
65
|
+
"""Update existing data in storage.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
context: Module context containing the storage strategy
|
|
69
|
+
collection: Collection name
|
|
70
|
+
record_id: Record identifier
|
|
71
|
+
data: Updated data
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Result from the storage strategy
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
StorageServiceError: If update operation fails
|
|
78
|
+
"""
|
|
79
|
+
return context.storage.update(collection, record_id, data)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core models."""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Job manager models."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
from digitalkin.core.job_manager.base_job_manager import BaseJobManager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JobManagerMode(Enum):
|
|
9
|
+
"""Job manager mode."""
|
|
10
|
+
|
|
11
|
+
SINGLE = "single"
|
|
12
|
+
TASKIQ = "taskiq"
|
|
13
|
+
|
|
14
|
+
def __str__(self) -> str:
|
|
15
|
+
"""Get the string representation of the job manager mode.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
str: job manager mode name.
|
|
19
|
+
"""
|
|
20
|
+
return self.value
|
|
21
|
+
|
|
22
|
+
def get_manager_class(self) -> type[BaseJobManager]:
|
|
23
|
+
"""Get the job manager class based on the mode.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
type: The job manager class.
|
|
27
|
+
"""
|
|
28
|
+
match self:
|
|
29
|
+
case JobManagerMode.SINGLE:
|
|
30
|
+
from digitalkin.core.job_manager.single_job_manager import SingleJobManager # noqa: PLC0415
|
|
31
|
+
|
|
32
|
+
return SingleJobManager
|
|
33
|
+
case JobManagerMode.TASKIQ:
|
|
34
|
+
from digitalkin.core.job_manager.taskiq_job_manager import TaskiqJobManager # noqa: PLC0415
|
|
35
|
+
|
|
36
|
+
return TaskiqJobManager
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Task monitoring models for signaling and heartbeat messages."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TaskStatus(Enum):
|
|
11
|
+
"""Task status enumeration."""
|
|
12
|
+
|
|
13
|
+
PENDING = "pending"
|
|
14
|
+
RUNNING = "running"
|
|
15
|
+
CANCELLED = "cancelled"
|
|
16
|
+
COMPLETED = "completed"
|
|
17
|
+
FAILED = "failed"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CancellationReason(Enum):
|
|
21
|
+
"""Reason for task cancellation - helps distinguish cleanup vs real cancellation."""
|
|
22
|
+
|
|
23
|
+
# Cleanup cancellations (not errors)
|
|
24
|
+
SUCCESS_CLEANUP = "success_cleanup" # Main task completed, cleaning up helper tasks
|
|
25
|
+
FAILURE_CLEANUP = "failure_cleanup" # Main task failed, cleaning up helper tasks
|
|
26
|
+
|
|
27
|
+
# Real cancellations
|
|
28
|
+
SIGNAL = "signal" # External signal requested cancellation
|
|
29
|
+
HEARTBEAT_FAILURE = "heartbeat_failure" # Heartbeat stopped working
|
|
30
|
+
TIMEOUT = "timeout" # Task timed out
|
|
31
|
+
SHUTDOWN = "shutdown" # Manager is shutting down
|
|
32
|
+
|
|
33
|
+
# Unknown/unset
|
|
34
|
+
UNKNOWN = "unknown" # Reason not determined
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SignalType(Enum):
|
|
38
|
+
"""Signal type enumeration."""
|
|
39
|
+
|
|
40
|
+
START = "start"
|
|
41
|
+
STOP = "stop"
|
|
42
|
+
CANCEL = "cancel"
|
|
43
|
+
PAUSE = "pause"
|
|
44
|
+
RESUME = "resume"
|
|
45
|
+
STATUS = "status"
|
|
46
|
+
|
|
47
|
+
ACK_CANCEL = "ack_cancel"
|
|
48
|
+
ACK_PAUSE = "ack_pause"
|
|
49
|
+
ACK_RESUME = "ack_resume"
|
|
50
|
+
ACK_STATUS = "ack_status"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SignalMessage(BaseModel):
|
|
54
|
+
"""Signal message model for task monitoring."""
|
|
55
|
+
|
|
56
|
+
task_id: str = Field(..., description="Unique identifier for the task")
|
|
57
|
+
mission_id: str = Field(..., description="Identifier for the mission")
|
|
58
|
+
status: TaskStatus = Field(..., description="Current status of the task")
|
|
59
|
+
action: SignalType = Field(..., description="Type of signal action")
|
|
60
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
61
|
+
payload: dict[str, Any] = Field(default={}, description="Optional payload for the signal")
|
|
62
|
+
model_config = {"use_enum_values": True}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class HeartbeatMessage(BaseModel):
|
|
66
|
+
"""Heartbeat message model for task monitoring."""
|
|
67
|
+
|
|
68
|
+
task_id: str = Field(..., description="Unique identifier for the task")
|
|
69
|
+
mission_id: str = Field(..., description="Identifier for the mission")
|
|
70
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Base gRPC server and client models."""
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Data models for gRPC server configurations."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, ValidationInfo, field_validator
|
|
8
|
+
|
|
9
|
+
from digitalkin.grpc_servers.utils.exceptions import ConfigurationError, SecurityError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ServerMode(str, Enum):
|
|
13
|
+
"""Enum for server operation mode."""
|
|
14
|
+
|
|
15
|
+
SYNC = "sync"
|
|
16
|
+
ASYNC = "async"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SecurityMode(str, Enum):
|
|
20
|
+
"""Enum for server security mode."""
|
|
21
|
+
|
|
22
|
+
SECURE = "secure"
|
|
23
|
+
INSECURE = "insecure"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ServerCredentials(BaseModel):
|
|
27
|
+
"""Model for server credentials in secure mode.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
server_key_path: Path to the server private key
|
|
31
|
+
server_cert_path: Path to the server certificate
|
|
32
|
+
root_cert_path: Optional path to the root certificate
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
server_key_path: Path = Field(..., description="Path to the server private key")
|
|
36
|
+
server_cert_path: Path = Field(..., description="Path to the server certificate")
|
|
37
|
+
root_cert_path: Path | None = Field(None, description="Path to the root certificate")
|
|
38
|
+
|
|
39
|
+
# Enable __slots__ for memory efficiency
|
|
40
|
+
model_config = {
|
|
41
|
+
"extra": "forbid",
|
|
42
|
+
"arbitrary_types_allowed": True,
|
|
43
|
+
"validate_assignment": True,
|
|
44
|
+
"use_enum_values": True,
|
|
45
|
+
"frozen": True, # Make immutable
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@field_validator("server_key_path", "server_cert_path", "root_cert_path")
|
|
49
|
+
@classmethod
|
|
50
|
+
def check_path_exists(cls, v: Path | None) -> Path | None:
|
|
51
|
+
"""Validate that the file path exists.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
v: Path to validate
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The validated path
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
SecurityError: If the path does not exist
|
|
61
|
+
"""
|
|
62
|
+
if v is not None and not v.exists():
|
|
63
|
+
msg = f"File not found: {v}"
|
|
64
|
+
raise SecurityError(msg)
|
|
65
|
+
return v
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ClientCredentials(BaseModel):
|
|
69
|
+
"""Model for client credentials in secure mode.
|
|
70
|
+
|
|
71
|
+
Attributes:
|
|
72
|
+
root_cert_path: path to the root certificate
|
|
73
|
+
client_key_path: Path to the client private key
|
|
74
|
+
client_cert_path: Path to the client certificate
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
root_cert_path: Path = Field(..., description="Path to the root certificate")
|
|
78
|
+
client_key_path: Path | None = Field(None, description="Path to the client private key | mTLS enable")
|
|
79
|
+
client_cert_path: Path | None = Field(None, description="Path to the client certificate | mTLS enable")
|
|
80
|
+
|
|
81
|
+
# Enable __slots__ for memory efficiency
|
|
82
|
+
model_config = {
|
|
83
|
+
"extra": "forbid",
|
|
84
|
+
"arbitrary_types_allowed": True,
|
|
85
|
+
"validate_assignment": True,
|
|
86
|
+
"use_enum_values": True,
|
|
87
|
+
"frozen": True, # Make immutable
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@field_validator("client_key_path", "client_cert_path", "root_cert_path")
|
|
91
|
+
@classmethod
|
|
92
|
+
def check_path_exists(cls, v: Path | None) -> Path | None:
|
|
93
|
+
"""Validate that the file path exists.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
v: Path to validate
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
The validated path
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
SecurityError: If the path does not exist
|
|
103
|
+
"""
|
|
104
|
+
if v is not None and not v.exists():
|
|
105
|
+
msg = f"File not found: {v}"
|
|
106
|
+
raise SecurityError(msg)
|
|
107
|
+
return v
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ChannelConfig(BaseModel):
|
|
111
|
+
"""Base configuration for gRPC channels.
|
|
112
|
+
|
|
113
|
+
Attributes:
|
|
114
|
+
host: Host address
|
|
115
|
+
port: Port to listen on
|
|
116
|
+
mode: communication operation mode (sync/async)
|
|
117
|
+
security: Security mode (secure/insecure)
|
|
118
|
+
credentials: Client credentials for secure mode
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
host: str = Field("0.0.0.0", description="Host address to bind the client to") # noqa: S104
|
|
122
|
+
port: int = Field(50051, description="Port to listen on")
|
|
123
|
+
mode: ServerMode = Field(ServerMode.SYNC, description="Client operation mode (sync/async)")
|
|
124
|
+
security: SecurityMode = Field(SecurityMode.INSECURE, description="Security mode (secure/insecure)")
|
|
125
|
+
|
|
126
|
+
# Enable __slots__ for memory efficiency
|
|
127
|
+
model_config = {
|
|
128
|
+
"extra": "forbid",
|
|
129
|
+
"arbitrary_types_allowed": True,
|
|
130
|
+
"validate_assignment": True,
|
|
131
|
+
"use_enum_values": True,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@field_validator("port")
|
|
135
|
+
@classmethod
|
|
136
|
+
def validate_port(cls, v: int) -> int:
|
|
137
|
+
"""Validate that the port is in a valid range.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
v: Port number to validate
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
The validated port number
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
ConfigurationError: If port is outside valid range
|
|
147
|
+
"""
|
|
148
|
+
if not 0 < v < 65536: # noqa: PLR2004
|
|
149
|
+
msg = f"Port must be between 1 and 65535, got {v}"
|
|
150
|
+
raise ConfigurationError(msg)
|
|
151
|
+
return v
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def address(self) -> str:
|
|
155
|
+
"""Get the server address.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
The formatted address string
|
|
159
|
+
"""
|
|
160
|
+
return f"{self.host}:{self.port}"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class ClientConfig(ChannelConfig):
|
|
164
|
+
"""Base configuration for gRPC clients.
|
|
165
|
+
|
|
166
|
+
Attributes:
|
|
167
|
+
host: Host address to bind the client to
|
|
168
|
+
port: Port to listen on
|
|
169
|
+
mode: Client operation mode (sync/async)
|
|
170
|
+
security: Security mode (secure/insecure)
|
|
171
|
+
credentials: Client credentials for secure mode
|
|
172
|
+
channel_options: Additional channel options
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
credentials: ClientCredentials | None = Field(None, description="Client credentials for secure mode")
|
|
176
|
+
channel_options: list[tuple[str, Any]] = Field(
|
|
177
|
+
default_factory=lambda: [
|
|
178
|
+
("grpc.max_receive_message_length", 100 * 1024 * 1024), # 100MB
|
|
179
|
+
("grpc.max_send_message_length", 100 * 1024 * 1024), # 100MB
|
|
180
|
+
],
|
|
181
|
+
description="Additional channel options",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@field_validator("credentials")
|
|
185
|
+
@classmethod
|
|
186
|
+
def validate_credentials(cls, v: ClientCredentials | None, info: ValidationInfo) -> ClientCredentials | None:
|
|
187
|
+
"""Validate that credentials are provided when in secure mode.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
v: The credentials value
|
|
191
|
+
info: ValidationInfo containing other field values
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The validated credentials
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
ConfigurationError: If credentials are missing in secure mode
|
|
198
|
+
"""
|
|
199
|
+
# Access security mode from the info.data dictionary
|
|
200
|
+
security = info.data.get("security")
|
|
201
|
+
|
|
202
|
+
if security == SecurityMode.SECURE and v is None:
|
|
203
|
+
msg = "Credentials must be provided when using secure mode"
|
|
204
|
+
raise ConfigurationError(msg)
|
|
205
|
+
return v
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class ServerConfig(ChannelConfig):
|
|
209
|
+
"""Base configuration for gRPC servers.
|
|
210
|
+
|
|
211
|
+
Attributes:
|
|
212
|
+
host: Host address to bind the server to
|
|
213
|
+
port: Port to listen on
|
|
214
|
+
max_workers: Maximum number of workers for sync mode
|
|
215
|
+
mode: Server operation mode (sync/async)
|
|
216
|
+
security: Security mode (secure/insecure)
|
|
217
|
+
credentials: Server credentials for secure mode
|
|
218
|
+
server_options: Additional server options
|
|
219
|
+
enable_reflection: Enable reflection for the server
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
max_workers: int = Field(10, description="Maximum number of workers for sync mode")
|
|
223
|
+
credentials: ServerCredentials | None = Field(None, description="Server credentials for secure mode")
|
|
224
|
+
server_options: list[tuple[str, Any]] = Field(
|
|
225
|
+
default_factory=lambda: [
|
|
226
|
+
("grpc.max_receive_message_length", 100 * 1024 * 1024), # 100MB
|
|
227
|
+
("grpc.max_send_message_length", 100 * 1024 * 1024), # 100MB
|
|
228
|
+
],
|
|
229
|
+
description="Additional server options",
|
|
230
|
+
)
|
|
231
|
+
enable_reflection: bool = Field(default=True, description="Enable reflection for the server")
|
|
232
|
+
enable_health_check: bool = Field(default=True, description="Enable health check service")
|
|
233
|
+
|
|
234
|
+
@field_validator("credentials")
|
|
235
|
+
@classmethod
|
|
236
|
+
def validate_credentials(cls, v: ServerCredentials | None, info: ValidationInfo) -> ServerCredentials | None:
|
|
237
|
+
"""Validate that credentials are provided when in secure mode.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
v: The credentials value
|
|
241
|
+
info: ValidationInfo containing other field values
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
The validated credentials
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
ConfigurationError: If credentials are missing in secure mode
|
|
248
|
+
"""
|
|
249
|
+
# Access security mode from the info.data dictionary
|
|
250
|
+
security = info.data.get("security")
|
|
251
|
+
|
|
252
|
+
if security == SecurityMode.SECURE and v is None:
|
|
253
|
+
msg = "Credentials must be provided when using secure mode"
|
|
254
|
+
raise ConfigurationError(msg)
|
|
255
|
+
return v
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class ModuleServerConfig(ServerConfig):
|
|
259
|
+
"""Configuration for Module gRPC server.
|
|
260
|
+
|
|
261
|
+
Attributes:
|
|
262
|
+
registry_address: Address of the registry server
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
registry_address: str = Field(..., description="Address of the registry server")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class RegistryServerConfig(ServerConfig):
|
|
269
|
+
"""Configuration for Registry gRPC server.
|
|
270
|
+
|
|
271
|
+
Attributes:
|
|
272
|
+
database_url: Database URL for registry data storage
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
database_url: str | None = Field(None, description="Database URL for registry data storage")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Type definitions for gRPC utilities."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol, TypeVar
|
|
4
|
+
|
|
5
|
+
import grpc
|
|
6
|
+
from grpc import aio as grpc_aio
|
|
7
|
+
|
|
8
|
+
GrpcServer = grpc.Server | grpc_aio.Server
|
|
9
|
+
|
|
10
|
+
# Create a type variable for servicer implementations
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ServiceObject(Protocol):
|
|
15
|
+
"""Protocol for individual services in a gRPC descriptor."""
|
|
16
|
+
|
|
17
|
+
full_name: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Create a protocol for service descriptors
|
|
21
|
+
class ServiceDescriptor(Protocol):
|
|
22
|
+
"""Protocol for gRPC service descriptors."""
|
|
23
|
+
|
|
24
|
+
services_by_name: dict[str, ServiceObject]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""This module contains the models for the modules."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.models.module.module_context import ModuleContext
|
|
4
|
+
from digitalkin.models.module.module_types import (
|
|
5
|
+
DataModel,
|
|
6
|
+
DataTrigger,
|
|
7
|
+
SetupModel,
|
|
8
|
+
)
|
|
9
|
+
from digitalkin.models.module.utility import (
|
|
10
|
+
EndOfStreamOutput,
|
|
11
|
+
UtilityProtocol,
|
|
12
|
+
UtilityRegistry,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
# Core types (used by all SDK users)
|
|
17
|
+
"DataModel",
|
|
18
|
+
"DataTrigger",
|
|
19
|
+
# Utility (commonly used)
|
|
20
|
+
"EndOfStreamOutput",
|
|
21
|
+
"ModuleContext",
|
|
22
|
+
"SetupModel",
|
|
23
|
+
"UtilityProtocol",
|
|
24
|
+
"UtilityRegistry",
|
|
25
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Module model."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum, auto
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ModuleCodeModel(BaseModel):
|
|
9
|
+
"""typed error/code model."""
|
|
10
|
+
|
|
11
|
+
code: str = Field(...)
|
|
12
|
+
message: str | None = Field(default=None)
|
|
13
|
+
short_description: str | None = Field(default=None)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ModuleStatus(Enum):
|
|
17
|
+
"""Possible module's state."""
|
|
18
|
+
|
|
19
|
+
CREATED = auto() # Module created but not started
|
|
20
|
+
STARTING = auto() # Module is starting
|
|
21
|
+
RUNNING = auto() # Module do run
|
|
22
|
+
STOPPING = auto() # Module is stopping
|
|
23
|
+
STOPPED = auto() # Module stop successfuly
|
|
24
|
+
FAILED = auto() # Module stopped due to internal error
|
|
25
|
+
CANCELLED = auto() # Module stopped due to internal error
|
|
26
|
+
NOT_FOUND = auto()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Module(BaseModel):
|
|
30
|
+
"""Module model."""
|
|
31
|
+
|
|
32
|
+
name: str
|
|
33
|
+
cost_schema: str
|
|
34
|
+
input_schema: str
|
|
35
|
+
output_schema: str
|
|
36
|
+
setup_schema: str
|
|
37
|
+
secret_schema: str
|
|
38
|
+
type: str
|
|
39
|
+
version: str
|
|
40
|
+
description: str
|