digitalkin 0.3.1__py3-none-any.whl → 0.3.1.dev0__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/core/job_manager/taskiq_broker.py +1 -1
- digitalkin/core/job_manager/taskiq_job_manager.py +2 -1
- digitalkin/core/task_manager/base_task_manager.py +12 -87
- digitalkin/core/task_manager/task_executor.py +27 -103
- digitalkin/core/task_manager/task_session.py +19 -75
- digitalkin/grpc_servers/module_servicer.py +9 -17
- digitalkin/models/core/task_monitor.py +0 -17
- digitalkin/models/grpc_servers/models.py +4 -4
- digitalkin/models/module/module_context.py +0 -5
- digitalkin/models/module/module_types.py +15 -299
- digitalkin/modules/_base_module.py +28 -66
- digitalkin/services/services_config.py +0 -11
- digitalkin/services/services_models.py +1 -3
- digitalkin/services/user_profile/__init__.py +0 -11
- digitalkin/services/user_profile/grpc_user_profile.py +2 -2
- digitalkin/utils/__init__.py +0 -28
- {digitalkin-0.3.1.dist-info → digitalkin-0.3.1.dev0.dist-info}/METADATA +5 -5
- {digitalkin-0.3.1.dist-info → digitalkin-0.3.1.dev0.dist-info}/RECORD +22 -24
- digitalkin/utils/dynamic_schema.py +0 -483
- modules/dynamic_setup_module.py +0 -362
- {digitalkin-0.3.1.dist-info → digitalkin-0.3.1.dev0.dist-info}/WHEEL +0 -0
- {digitalkin-0.3.1.dist-info → digitalkin-0.3.1.dev0.dist-info}/licenses/LICENSE +0 -0
- {digitalkin-0.3.1.dist-info → digitalkin-0.3.1.dev0.dist-info}/top_level.txt +0 -0
|
@@ -175,8 +175,8 @@ class ClientConfig(ChannelConfig):
|
|
|
175
175
|
credentials: ClientCredentials | None = Field(None, description="Client credentials for secure mode")
|
|
176
176
|
channel_options: list[tuple[str, Any]] = Field(
|
|
177
177
|
default_factory=lambda: [
|
|
178
|
-
("grpc.max_receive_message_length",
|
|
179
|
-
("grpc.max_send_message_length",
|
|
178
|
+
("grpc.max_receive_message_length", 50 * 1024 * 1024), # 50MB
|
|
179
|
+
("grpc.max_send_message_length", 50 * 1024 * 1024), # 50MB
|
|
180
180
|
],
|
|
181
181
|
description="Additional channel options",
|
|
182
182
|
)
|
|
@@ -223,8 +223,8 @@ class ServerConfig(ChannelConfig):
|
|
|
223
223
|
credentials: ServerCredentials | None = Field(None, description="Server credentials for secure mode")
|
|
224
224
|
server_options: list[tuple[str, Any]] = Field(
|
|
225
225
|
default_factory=lambda: [
|
|
226
|
-
("grpc.max_receive_message_length",
|
|
227
|
-
("grpc.max_send_message_length",
|
|
226
|
+
("grpc.max_receive_message_length", 50 * 1024 * 1024), # 50MB
|
|
227
|
+
("grpc.max_send_message_length", 50 * 1024 * 1024), # 50MB
|
|
228
228
|
],
|
|
229
229
|
description="Additional server options",
|
|
230
230
|
)
|
|
@@ -10,7 +10,6 @@ from digitalkin.services.identity.identity_strategy import IdentityStrategy
|
|
|
10
10
|
from digitalkin.services.registry.registry_strategy import RegistryStrategy
|
|
11
11
|
from digitalkin.services.snapshot.snapshot_strategy import SnapshotStrategy
|
|
12
12
|
from digitalkin.services.storage.storage_strategy import StorageStrategy
|
|
13
|
-
from digitalkin.services.user_profile.user_profile_strategy import UserProfileStrategy
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class Session(SimpleNamespace):
|
|
@@ -90,7 +89,6 @@ class ModuleContext:
|
|
|
90
89
|
registry: RegistryStrategy
|
|
91
90
|
snapshot: SnapshotStrategy
|
|
92
91
|
storage: StorageStrategy
|
|
93
|
-
user_profile: UserProfileStrategy
|
|
94
92
|
|
|
95
93
|
session: Session
|
|
96
94
|
callbacks: SimpleNamespace
|
|
@@ -107,7 +105,6 @@ class ModuleContext:
|
|
|
107
105
|
registry: RegistryStrategy,
|
|
108
106
|
snapshot: SnapshotStrategy,
|
|
109
107
|
storage: StorageStrategy,
|
|
110
|
-
user_profile: UserProfileStrategy,
|
|
111
108
|
session: dict[str, Any],
|
|
112
109
|
metadata: dict[str, Any] = {},
|
|
113
110
|
helpers: dict[str, Any] = {},
|
|
@@ -123,7 +120,6 @@ class ModuleContext:
|
|
|
123
120
|
registry: RegistryStrategy.
|
|
124
121
|
snapshot: SnapshotStrategy.
|
|
125
122
|
storage: StorageStrategy.
|
|
126
|
-
user_profile: UserProfileStrategy.
|
|
127
123
|
metadata: dict defining differents Module metadata.
|
|
128
124
|
helpers: dict different user defined helpers.
|
|
129
125
|
session: dict referring the session IDs or informations.
|
|
@@ -137,7 +133,6 @@ class ModuleContext:
|
|
|
137
133
|
self.registry = registry
|
|
138
134
|
self.snapshot = snapshot
|
|
139
135
|
self.storage = storage
|
|
140
|
-
self.user_profile = user_profile
|
|
141
136
|
|
|
142
137
|
self.metadata = SimpleNamespace(**metadata)
|
|
143
138
|
self.session = Session(**session)
|
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
"""Types for module models."""
|
|
2
2
|
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import copy
|
|
6
|
-
import types
|
|
7
|
-
import typing
|
|
8
3
|
from datetime import datetime, timezone
|
|
9
|
-
from typing import
|
|
4
|
+
from typing import Any, ClassVar, Generic, TypeVar, cast
|
|
10
5
|
|
|
11
6
|
from pydantic import BaseModel, ConfigDict, Field, create_model
|
|
12
7
|
|
|
13
8
|
from digitalkin.logger import logger
|
|
14
|
-
from digitalkin.utils.dynamic_schema import (
|
|
15
|
-
DynamicField,
|
|
16
|
-
get_fetchers,
|
|
17
|
-
has_dynamic,
|
|
18
|
-
resolve_safe,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
if TYPE_CHECKING:
|
|
22
|
-
from pydantic.fields import FieldInfo
|
|
23
9
|
|
|
24
10
|
|
|
25
11
|
class DataTrigger(BaseModel):
|
|
@@ -75,50 +61,27 @@ SetupModelT = TypeVar("SetupModelT", bound="SetupModel")
|
|
|
75
61
|
class SetupModel(BaseModel):
|
|
76
62
|
"""Base definition of setup model showing mandatory root fields.
|
|
77
63
|
|
|
78
|
-
Optionally, the setup model can define a config option in json_schema_extra
|
|
79
|
-
to be used to initialize the Kin. Supports dynamic schema providers for
|
|
80
|
-
runtime value generation.
|
|
81
|
-
|
|
82
|
-
Attributes:
|
|
83
|
-
model_fields: Inherited from Pydantic BaseModel, contains field definitions.
|
|
64
|
+
Optionally, the setup model can define a config option in json_schema_extra to be used to initialize the Kin.
|
|
84
65
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
66
|
+
Example:
|
|
67
|
+
class MySetup(SetupModel):
|
|
68
|
+
name: str = Field()
|
|
69
|
+
number: int = Field(..., json_schema_extra={"config": True})
|
|
88
70
|
"""
|
|
89
71
|
|
|
90
72
|
@classmethod
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
*,
|
|
94
|
-
config_fields: bool,
|
|
95
|
-
hidden_fields: bool,
|
|
96
|
-
force: bool = False,
|
|
97
|
-
) -> type[SetupModelT]:
|
|
98
|
-
"""Dynamically builds and returns a new BaseModel subclass with filtered fields.
|
|
99
|
-
|
|
100
|
-
This method filters fields based on their `json_schema_extra` metadata:
|
|
101
|
-
- Fields with `{"config": True}` are included only when `config_fields=True`
|
|
102
|
-
- Fields with `{"hidden": True}` are included only when `hidden_fields=True`
|
|
103
|
-
|
|
104
|
-
When `force=True`, fields with dynamic schema providers will have their
|
|
105
|
-
providers called to fetch fresh values for schema metadata like enums.
|
|
106
|
-
This includes recursively processing nested BaseModel fields.
|
|
73
|
+
def get_clean_model(cls, *, config_fields: bool, hidden_fields: bool) -> type[SetupModelT]: # type: ignore
|
|
74
|
+
"""Dynamically builds and returns a new BaseModel subclass.
|
|
107
75
|
|
|
108
|
-
|
|
109
|
-
config_fields: If True, include fields marked with `{"config": True}`.
|
|
110
|
-
These are typically initial configuration fields.
|
|
111
|
-
hidden_fields: If True, include fields marked with `{"hidden": True}`.
|
|
112
|
-
These are typically runtime-only fields not shown in initial config.
|
|
113
|
-
force: If True, refresh dynamic schema fields by calling their providers.
|
|
114
|
-
Use this when you need up-to-date values from external sources like
|
|
115
|
-
databases or APIs. Default is False for performance.
|
|
76
|
+
containing only those fields where json_schema_extra["config"] == True.
|
|
116
77
|
|
|
117
78
|
Returns:
|
|
118
|
-
A new BaseModel subclass with filtered fields.
|
|
79
|
+
Type[BaseModel]: A new BaseModel subclass with the filtered fields.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If both config_fields and hidden_fields are set to True.
|
|
119
83
|
"""
|
|
120
84
|
clean_fields: dict[str, Any] = {}
|
|
121
|
-
|
|
122
85
|
for name, field_info in cls.model_fields.items():
|
|
123
86
|
extra = getattr(field_info, "json_schema_extra", {}) or {}
|
|
124
87
|
is_config = bool(extra.get("config", False))
|
|
@@ -134,27 +97,7 @@ class SetupModel(BaseModel):
|
|
|
134
97
|
logger.debug("Skipping '%s' (hidden-only)", name)
|
|
135
98
|
continue
|
|
136
99
|
|
|
137
|
-
|
|
138
|
-
current_field_info = field_info
|
|
139
|
-
current_annotation = field_info.annotation
|
|
140
|
-
|
|
141
|
-
if force:
|
|
142
|
-
# Check if this field has DynamicField metadata
|
|
143
|
-
if has_dynamic(field_info):
|
|
144
|
-
current_field_info = await cls._refresh_field_schema(name, field_info)
|
|
145
|
-
|
|
146
|
-
# Check if the annotation is a nested BaseModel that might have dynamic fields
|
|
147
|
-
nested_model = cls._get_base_model_type(current_annotation)
|
|
148
|
-
if nested_model is not None:
|
|
149
|
-
refreshed_nested = await cls._refresh_nested_model(nested_model)
|
|
150
|
-
if refreshed_nested is not nested_model:
|
|
151
|
-
# Update annotation to use refreshed nested model
|
|
152
|
-
current_annotation = refreshed_nested
|
|
153
|
-
# Create new field_info with updated annotation (deep copy for safety)
|
|
154
|
-
current_field_info = copy.deepcopy(current_field_info)
|
|
155
|
-
setattr(current_field_info, "annotation", current_annotation)
|
|
156
|
-
|
|
157
|
-
clean_fields[name] = (current_annotation, current_field_info)
|
|
100
|
+
clean_fields[name] = (field_info.annotation, field_info)
|
|
158
101
|
|
|
159
102
|
# Dynamically create a model e.g. "SetupModel"
|
|
160
103
|
m = create_model(
|
|
@@ -163,231 +106,4 @@ class SetupModel(BaseModel):
|
|
|
163
106
|
__config__=ConfigDict(arbitrary_types_allowed=True),
|
|
164
107
|
**clean_fields,
|
|
165
108
|
)
|
|
166
|
-
return cast("type[SetupModelT]", m)
|
|
167
|
-
|
|
168
|
-
@classmethod
|
|
169
|
-
def _get_base_model_type(cls, annotation: type | None) -> type[BaseModel] | None:
|
|
170
|
-
"""Extract BaseModel type from an annotation.
|
|
171
|
-
|
|
172
|
-
Handles direct types, Optional, Union, list, dict, set, tuple, and other generics.
|
|
173
|
-
|
|
174
|
-
Args:
|
|
175
|
-
annotation: The type annotation to inspect.
|
|
176
|
-
|
|
177
|
-
Returns:
|
|
178
|
-
The BaseModel subclass if found, None otherwise.
|
|
179
|
-
"""
|
|
180
|
-
if annotation is None:
|
|
181
|
-
return None
|
|
182
|
-
|
|
183
|
-
# Direct BaseModel subclass check
|
|
184
|
-
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
|
185
|
-
return annotation
|
|
186
|
-
|
|
187
|
-
origin = get_origin(annotation)
|
|
188
|
-
if origin is None:
|
|
189
|
-
return None
|
|
190
|
-
|
|
191
|
-
args = get_args(annotation)
|
|
192
|
-
return cls._extract_base_model_from_args(origin, args)
|
|
193
|
-
|
|
194
|
-
@classmethod
|
|
195
|
-
def _extract_base_model_from_args(
|
|
196
|
-
cls,
|
|
197
|
-
origin: type,
|
|
198
|
-
args: tuple[type, ...],
|
|
199
|
-
) -> type[BaseModel] | None:
|
|
200
|
-
"""Extract BaseModel from generic type arguments.
|
|
201
|
-
|
|
202
|
-
Args:
|
|
203
|
-
origin: The generic origin type (list, dict, Union, etc.).
|
|
204
|
-
args: The type arguments.
|
|
205
|
-
|
|
206
|
-
Returns:
|
|
207
|
-
The BaseModel subclass if found, None otherwise.
|
|
208
|
-
"""
|
|
209
|
-
# Union/Optional: check each arg (supports both typing.Union and types.UnionType)
|
|
210
|
-
# Python 3.10+ uses types.UnionType for X | Y syntax
|
|
211
|
-
if origin is typing.Union or origin is types.UnionType:
|
|
212
|
-
return cls._find_base_model_in_args(args)
|
|
213
|
-
|
|
214
|
-
# list, set, frozenset: check first arg
|
|
215
|
-
if origin in {list, set, frozenset} and args:
|
|
216
|
-
return cls._check_base_model(args[0])
|
|
217
|
-
|
|
218
|
-
# dict: check value type (second arg)
|
|
219
|
-
dict_value_index = 1
|
|
220
|
-
if origin is dict and len(args) > dict_value_index:
|
|
221
|
-
return cls._check_base_model(args[dict_value_index])
|
|
222
|
-
|
|
223
|
-
# tuple: check first non-ellipsis arg
|
|
224
|
-
if origin is tuple:
|
|
225
|
-
return cls._find_base_model_in_args(args, skip_ellipsis=True)
|
|
226
|
-
|
|
227
|
-
return None
|
|
228
|
-
|
|
229
|
-
@classmethod
|
|
230
|
-
def _check_base_model(cls, arg: type) -> type[BaseModel] | None:
|
|
231
|
-
"""Check if arg is a BaseModel subclass.
|
|
232
|
-
|
|
233
|
-
Returns:
|
|
234
|
-
The BaseModel subclass if arg is one, None otherwise.
|
|
235
|
-
"""
|
|
236
|
-
if isinstance(arg, type) and issubclass(arg, BaseModel):
|
|
237
|
-
return arg
|
|
238
|
-
return None
|
|
239
|
-
|
|
240
|
-
@classmethod
|
|
241
|
-
def _find_base_model_in_args(
|
|
242
|
-
cls,
|
|
243
|
-
args: tuple[type, ...],
|
|
244
|
-
*,
|
|
245
|
-
skip_ellipsis: bool = False,
|
|
246
|
-
) -> type[BaseModel] | None:
|
|
247
|
-
"""Find first BaseModel in args.
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
The first BaseModel subclass found, None otherwise.
|
|
251
|
-
"""
|
|
252
|
-
for arg in args:
|
|
253
|
-
if arg is type(None):
|
|
254
|
-
continue
|
|
255
|
-
if skip_ellipsis and arg is ...:
|
|
256
|
-
continue
|
|
257
|
-
result = cls._check_base_model(arg)
|
|
258
|
-
if result is not None:
|
|
259
|
-
return result
|
|
260
|
-
return None
|
|
261
|
-
|
|
262
|
-
@classmethod
|
|
263
|
-
async def _refresh_nested_model(cls, model_cls: type[BaseModel]) -> type[BaseModel]:
|
|
264
|
-
"""Refresh dynamic fields in a nested BaseModel.
|
|
265
|
-
|
|
266
|
-
Creates a new model class with all DynamicField metadata resolved.
|
|
267
|
-
|
|
268
|
-
Args:
|
|
269
|
-
model_cls: The nested model class to refresh.
|
|
270
|
-
|
|
271
|
-
Returns:
|
|
272
|
-
A new model class with refreshed fields, or the original if no changes.
|
|
273
|
-
"""
|
|
274
|
-
has_changes = False
|
|
275
|
-
clean_fields: dict[str, Any] = {}
|
|
276
|
-
|
|
277
|
-
for name, field_info in model_cls.model_fields.items():
|
|
278
|
-
current_field_info = field_info
|
|
279
|
-
current_annotation = field_info.annotation
|
|
280
|
-
|
|
281
|
-
# Check if field has DynamicField metadata
|
|
282
|
-
if has_dynamic(field_info):
|
|
283
|
-
current_field_info = await cls._refresh_field_schema(name, field_info)
|
|
284
|
-
has_changes = True
|
|
285
|
-
|
|
286
|
-
# Recursively check nested models
|
|
287
|
-
nested_model = cls._get_base_model_type(current_annotation)
|
|
288
|
-
if nested_model is not None:
|
|
289
|
-
refreshed_nested = await cls._refresh_nested_model(nested_model)
|
|
290
|
-
if refreshed_nested is not nested_model:
|
|
291
|
-
current_annotation = refreshed_nested
|
|
292
|
-
current_field_info = copy.deepcopy(current_field_info)
|
|
293
|
-
setattr(current_field_info, "annotation", current_annotation)
|
|
294
|
-
has_changes = True
|
|
295
|
-
|
|
296
|
-
clean_fields[name] = (current_annotation, current_field_info)
|
|
297
|
-
|
|
298
|
-
if not has_changes:
|
|
299
|
-
return model_cls
|
|
300
|
-
|
|
301
|
-
# Create new model with refreshed fields
|
|
302
|
-
logger.debug("Creating refreshed nested model for '%s'", model_cls.__name__)
|
|
303
|
-
return create_model(
|
|
304
|
-
model_cls.__name__,
|
|
305
|
-
__base__=BaseModel,
|
|
306
|
-
__config__=ConfigDict(arbitrary_types_allowed=True),
|
|
307
|
-
**clean_fields,
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
@classmethod
|
|
311
|
-
async def _refresh_field_schema(cls, field_name: str, field_info: FieldInfo) -> FieldInfo:
|
|
312
|
-
"""Refresh a field's json_schema_extra with fresh values from dynamic providers.
|
|
313
|
-
|
|
314
|
-
This method calls all dynamic providers registered for a field (via Annotated
|
|
315
|
-
metadata) and creates a new FieldInfo with the resolved values. The original
|
|
316
|
-
field_info is not modified.
|
|
317
|
-
|
|
318
|
-
Uses `resolve_safe()` for structured error handling, allowing partial success
|
|
319
|
-
when some fetchers fail. Successfully resolved values are still applied.
|
|
320
|
-
|
|
321
|
-
Args:
|
|
322
|
-
field_name: The name of the field being refreshed (used for logging).
|
|
323
|
-
field_info: The original FieldInfo object containing the dynamic providers.
|
|
324
|
-
|
|
325
|
-
Returns:
|
|
326
|
-
A new FieldInfo object with the same attributes as the original, but with
|
|
327
|
-
`json_schema_extra` containing resolved values and Dynamic metadata removed.
|
|
328
|
-
|
|
329
|
-
Note:
|
|
330
|
-
If all fetchers fail, the original field_info is returned unchanged.
|
|
331
|
-
If some fetchers fail, successfully resolved values are still applied.
|
|
332
|
-
"""
|
|
333
|
-
fetchers = get_fetchers(field_info)
|
|
334
|
-
|
|
335
|
-
if not fetchers:
|
|
336
|
-
return field_info
|
|
337
|
-
|
|
338
|
-
fetcher_keys = list(fetchers.keys())
|
|
339
|
-
logger.debug(
|
|
340
|
-
"Refreshing dynamic schema for field '%s' with fetchers: %s",
|
|
341
|
-
field_name,
|
|
342
|
-
fetcher_keys,
|
|
343
|
-
extra={"field_name": field_name, "fetcher_keys": fetcher_keys},
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
# Resolve all fetchers with structured error handling
|
|
347
|
-
result = await resolve_safe(fetchers)
|
|
348
|
-
|
|
349
|
-
# Log any errors that occurred with full details
|
|
350
|
-
if result.errors:
|
|
351
|
-
for key, error in result.errors.items():
|
|
352
|
-
logger.warning(
|
|
353
|
-
"Failed to resolve '%s' for field '%s': %s: %s",
|
|
354
|
-
key,
|
|
355
|
-
field_name,
|
|
356
|
-
type(error).__name__,
|
|
357
|
-
str(error) or "(no message)",
|
|
358
|
-
extra={
|
|
359
|
-
"field_name": field_name,
|
|
360
|
-
"fetcher_key": key,
|
|
361
|
-
"error_type": type(error).__name__,
|
|
362
|
-
"error_message": str(error),
|
|
363
|
-
"error_repr": repr(error),
|
|
364
|
-
},
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
# If no values were resolved, return original field_info
|
|
368
|
-
if not result.values:
|
|
369
|
-
logger.warning(
|
|
370
|
-
"All fetchers failed for field '%s', keeping original",
|
|
371
|
-
field_name,
|
|
372
|
-
)
|
|
373
|
-
return field_info
|
|
374
|
-
|
|
375
|
-
# Build new json_schema_extra with resolved values merged
|
|
376
|
-
extra = getattr(field_info, "json_schema_extra", {}) or {}
|
|
377
|
-
new_extra = {**extra, **result.values}
|
|
378
|
-
|
|
379
|
-
# Create a deep copy of the FieldInfo to avoid shared mutable state
|
|
380
|
-
new_field_info = copy.deepcopy(field_info)
|
|
381
|
-
setattr(new_field_info, "json_schema_extra", new_extra)
|
|
382
|
-
|
|
383
|
-
# Remove Dynamic from metadata (it's been resolved)
|
|
384
|
-
new_metadata = [m for m in new_field_info.metadata if not isinstance(m, DynamicField)]
|
|
385
|
-
setattr(new_field_info, "metadata", new_metadata)
|
|
386
|
-
|
|
387
|
-
logger.debug(
|
|
388
|
-
"Refreshed '%s' with dynamic values: %s",
|
|
389
|
-
field_name,
|
|
390
|
-
list(result.values.keys()),
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
return new_field_info
|
|
109
|
+
return cast("type[SetupModelT]", m) # type: ignore
|
|
@@ -107,18 +107,14 @@ class BaseModule( # noqa: PLR0904
|
|
|
107
107
|
return self._status
|
|
108
108
|
|
|
109
109
|
@classmethod
|
|
110
|
-
|
|
110
|
+
def get_secret_format(cls, *, llm_format: bool) -> str:
|
|
111
111
|
"""Get the JSON schema of the secret format model.
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
references and simplified structure.
|
|
113
|
+
Raises:
|
|
114
|
+
NotImplementedError: If the `secret_format` is not defined.
|
|
116
115
|
|
|
117
116
|
Returns:
|
|
118
|
-
The JSON schema of the secret format as a
|
|
119
|
-
|
|
120
|
-
Raises:
|
|
121
|
-
NotImplementedError: If the `secret_format` class attribute is not defined.
|
|
117
|
+
The JSON schema of the secret format as a string.
|
|
122
118
|
"""
|
|
123
119
|
if cls.secret_format is not None:
|
|
124
120
|
if llm_format:
|
|
@@ -128,18 +124,14 @@ class BaseModule( # noqa: PLR0904
|
|
|
128
124
|
raise NotImplementedError(msg)
|
|
129
125
|
|
|
130
126
|
@classmethod
|
|
131
|
-
|
|
127
|
+
def get_input_format(cls, *, llm_format: bool) -> str:
|
|
132
128
|
"""Get the JSON schema of the input format model.
|
|
133
129
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
references and simplified structure.
|
|
130
|
+
Raises:
|
|
131
|
+
NotImplementedError: If the `input_format` is not defined.
|
|
137
132
|
|
|
138
133
|
Returns:
|
|
139
|
-
The JSON schema of the input format as a
|
|
140
|
-
|
|
141
|
-
Raises:
|
|
142
|
-
NotImplementedError: If the `input_format` class attribute is not defined.
|
|
134
|
+
The JSON schema of the input format as a string.
|
|
143
135
|
"""
|
|
144
136
|
if cls.input_format is not None:
|
|
145
137
|
if llm_format:
|
|
@@ -149,18 +141,14 @@ class BaseModule( # noqa: PLR0904
|
|
|
149
141
|
raise NotImplementedError(msg)
|
|
150
142
|
|
|
151
143
|
@classmethod
|
|
152
|
-
|
|
144
|
+
def get_output_format(cls, *, llm_format: bool) -> str:
|
|
153
145
|
"""Get the JSON schema of the output format model.
|
|
154
146
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
references and simplified structure.
|
|
147
|
+
Raises:
|
|
148
|
+
NotImplementedError: If the `output_format` is not defined.
|
|
158
149
|
|
|
159
150
|
Returns:
|
|
160
|
-
The JSON schema of the output format as a
|
|
161
|
-
|
|
162
|
-
Raises:
|
|
163
|
-
NotImplementedError: If the `output_format` class attribute is not defined.
|
|
151
|
+
The JSON schema of the output format as a string.
|
|
164
152
|
"""
|
|
165
153
|
if cls.output_format is not None:
|
|
166
154
|
if llm_format:
|
|
@@ -170,29 +158,20 @@ class BaseModule( # noqa: PLR0904
|
|
|
170
158
|
raise NotImplementedError(msg)
|
|
171
159
|
|
|
172
160
|
@classmethod
|
|
173
|
-
|
|
161
|
+
def get_config_setup_format(cls, *, llm_format: bool) -> str:
|
|
174
162
|
"""Gets the JSON schema of the config setup format model.
|
|
175
163
|
|
|
176
|
-
The config setup format is used only to initialize the module with configuration
|
|
177
|
-
|
|
178
|
-
excludes hidden runtime fields.
|
|
179
|
-
|
|
180
|
-
Dynamic schema fields are always resolved when generating the schema, as this
|
|
181
|
-
method is typically called during module discovery or schema generation where
|
|
182
|
-
fresh values are needed.
|
|
164
|
+
The config setup format is used only to initialize the module with configuration data.
|
|
165
|
+
The setup format is used to initialize an run the module with setup data.
|
|
183
166
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
references and simplified structure.
|
|
167
|
+
Raises:
|
|
168
|
+
NotImplementedError: If the `setup_format` is not defined.
|
|
187
169
|
|
|
188
170
|
Returns:
|
|
189
|
-
The JSON schema of the config setup format as a
|
|
190
|
-
|
|
191
|
-
Raises:
|
|
192
|
-
NotImplementedError: If the `setup_format` class attribute is not defined.
|
|
171
|
+
The JSON schema of the config setup format as a string.
|
|
193
172
|
"""
|
|
194
173
|
if cls.setup_format is not None:
|
|
195
|
-
setup_format =
|
|
174
|
+
setup_format = cls.setup_format.get_clean_model(config_fields=True, hidden_fields=False)
|
|
196
175
|
if llm_format:
|
|
197
176
|
return json.dumps(llm_ready_schema(setup_format), indent=2)
|
|
198
177
|
return json.dumps(setup_format.model_json_schema(), indent=2)
|
|
@@ -200,28 +179,17 @@ class BaseModule( # noqa: PLR0904
|
|
|
200
179
|
raise NotImplementedError(msg)
|
|
201
180
|
|
|
202
181
|
@classmethod
|
|
203
|
-
|
|
182
|
+
def get_setup_format(cls, *, llm_format: bool) -> str:
|
|
204
183
|
"""Gets the JSON schema of the setup format model.
|
|
205
184
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Dynamic schema fields are always resolved when generating the schema, as this
|
|
210
|
-
method is typically called during module discovery or schema generation where
|
|
211
|
-
fresh values are needed.
|
|
212
|
-
|
|
213
|
-
Args:
|
|
214
|
-
llm_format: If True, return LLM-optimized schema format with inlined
|
|
215
|
-
references and simplified structure.
|
|
185
|
+
Raises:
|
|
186
|
+
NotImplementedError: If the `setup_format` is not defined.
|
|
216
187
|
|
|
217
188
|
Returns:
|
|
218
|
-
The JSON schema of the setup format as a
|
|
219
|
-
|
|
220
|
-
Raises:
|
|
221
|
-
NotImplementedError: If the `setup_format` class attribute is not defined.
|
|
189
|
+
The JSON schema of the setup format as a string.
|
|
222
190
|
"""
|
|
223
191
|
if cls.setup_format is not None:
|
|
224
|
-
setup_format =
|
|
192
|
+
setup_format = cls.setup_format.get_clean_model(config_fields=False, hidden_fields=True)
|
|
225
193
|
if llm_format:
|
|
226
194
|
return json.dumps(llm_ready_schema(setup_format), indent=2)
|
|
227
195
|
return json.dumps(setup_format.model_json_schema(), indent=2)
|
|
@@ -253,22 +221,17 @@ class BaseModule( # noqa: PLR0904
|
|
|
253
221
|
return cls.input_format(**input_data)
|
|
254
222
|
|
|
255
223
|
@classmethod
|
|
256
|
-
|
|
224
|
+
def create_setup_model(cls, setup_data: dict[str, Any], *, config_fields: bool = False) -> SetupModelT:
|
|
257
225
|
"""Create the setup model from the setup data.
|
|
258
226
|
|
|
259
|
-
Creates a filtered setup model instance based on the provided data.
|
|
260
|
-
Uses `get_clean_model()` internally to get the appropriate model class
|
|
261
|
-
with field filtering applied.
|
|
262
|
-
|
|
263
227
|
Args:
|
|
264
228
|
setup_data: The setup data to create the model from.
|
|
265
229
|
config_fields: If True, include only fields with json_schema_extra["config"] == True.
|
|
266
230
|
|
|
267
231
|
Returns:
|
|
268
|
-
|
|
232
|
+
The setup model.
|
|
269
233
|
"""
|
|
270
|
-
|
|
271
|
-
return model_cls(**setup_data)
|
|
234
|
+
return cls.setup_format.get_clean_model(config_fields=config_fields, hidden_fields=True)(**setup_data)
|
|
272
235
|
|
|
273
236
|
@classmethod
|
|
274
237
|
def create_secret_model(cls, secret_data: dict[str, Any]) -> SecretModelT:
|
|
@@ -473,8 +436,7 @@ class BaseModule( # noqa: PLR0904
|
|
|
473
436
|
|
|
474
437
|
wrapper = config_setup_data.model_dump()
|
|
475
438
|
wrapper["content"] = content.model_dump()
|
|
476
|
-
|
|
477
|
-
await callback(setup_model)
|
|
439
|
+
await callback(self.create_setup_model(wrapper))
|
|
478
440
|
self._status = ModuleStatus.STOPPING
|
|
479
441
|
except Exception:
|
|
480
442
|
logger.error("Error during module lifecyle")
|
|
@@ -12,7 +12,6 @@ from digitalkin.services.registry import DefaultRegistry, RegistryStrategy
|
|
|
12
12
|
from digitalkin.services.services_models import ServicesMode, ServicesStrategy
|
|
13
13
|
from digitalkin.services.snapshot import DefaultSnapshot, SnapshotStrategy
|
|
14
14
|
from digitalkin.services.storage import DefaultStorage, GrpcStorage, StorageStrategy
|
|
15
|
-
from digitalkin.services.user_profile import DefaultUserProfile, GrpcUserProfile, UserProfileStrategy
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class ServicesConfig(BaseModel):
|
|
@@ -54,10 +53,6 @@ class ServicesConfig(BaseModel):
|
|
|
54
53
|
default_factory=lambda: ServicesStrategy(local=DefaultIdentity, remote=DefaultIdentity)
|
|
55
54
|
)
|
|
56
55
|
_config_identity: dict[str, Any | None] = PrivateAttr(default_factory=dict)
|
|
57
|
-
_user_profile: ServicesStrategy[UserProfileStrategy] = PrivateAttr(
|
|
58
|
-
default_factory=lambda: ServicesStrategy(local=DefaultUserProfile, remote=GrpcUserProfile)
|
|
59
|
-
)
|
|
60
|
-
_config_user_profile: dict[str, Any | None] = PrivateAttr(default_factory=dict)
|
|
61
56
|
|
|
62
57
|
# List of valid strategy names for validation
|
|
63
58
|
_valid_strategy_names: ClassVar[set[str]] = {
|
|
@@ -68,7 +63,6 @@ class ServicesConfig(BaseModel):
|
|
|
68
63
|
"filesystem",
|
|
69
64
|
"agent",
|
|
70
65
|
"identity",
|
|
71
|
-
"user_profile",
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
def __init__(
|
|
@@ -175,11 +169,6 @@ class ServicesConfig(BaseModel):
|
|
|
175
169
|
"""Get the identity service strategy class based on the current mode."""
|
|
176
170
|
return self._identity[self.mode.value]
|
|
177
171
|
|
|
178
|
-
@property
|
|
179
|
-
def user_profile(self) -> type[UserProfileStrategy]:
|
|
180
|
-
"""Get the user_profile service strategy class based on the current mode."""
|
|
181
|
-
return self._user_profile[self.mode.value]
|
|
182
|
-
|
|
183
172
|
def update_mode(self, mode: ServicesMode) -> None:
|
|
184
173
|
"""Update the strategy mode.
|
|
185
174
|
|
|
@@ -13,7 +13,6 @@ from digitalkin.services.identity import IdentityStrategy
|
|
|
13
13
|
from digitalkin.services.registry import RegistryStrategy
|
|
14
14
|
from digitalkin.services.snapshot import SnapshotStrategy
|
|
15
15
|
from digitalkin.services.storage import StorageStrategy
|
|
16
|
-
from digitalkin.services.user_profile import UserProfileStrategy
|
|
17
16
|
|
|
18
17
|
# Define type variables
|
|
19
18
|
T = TypeVar(
|
|
@@ -24,8 +23,7 @@ T = TypeVar(
|
|
|
24
23
|
| IdentityStrategy
|
|
25
24
|
| RegistryStrategy
|
|
26
25
|
| SnapshotStrategy
|
|
27
|
-
| StorageStrategy
|
|
28
|
-
| UserProfileStrategy,
|
|
26
|
+
| StorageStrategy,
|
|
29
27
|
)
|
|
30
28
|
|
|
31
29
|
|
|
@@ -1,12 +1 @@
|
|
|
1
1
|
"""UserProfile service package."""
|
|
2
|
-
|
|
3
|
-
from digitalkin.services.user_profile.default_user_profile import DefaultUserProfile
|
|
4
|
-
from digitalkin.services.user_profile.grpc_user_profile import GrpcUserProfile
|
|
5
|
-
from digitalkin.services.user_profile.user_profile_strategy import UserProfileServiceError, UserProfileStrategy
|
|
6
|
-
|
|
7
|
-
__all__ = [
|
|
8
|
-
"DefaultUserProfile",
|
|
9
|
-
"GrpcUserProfile",
|
|
10
|
-
"UserProfileServiceError",
|
|
11
|
-
"UserProfileStrategy",
|
|
12
|
-
]
|
|
@@ -49,8 +49,8 @@ class GrpcUserProfile(UserProfileStrategy, GrpcClientWrapper, GrpcErrorHandlerMi
|
|
|
49
49
|
ServerError: If gRPC operation fails
|
|
50
50
|
"""
|
|
51
51
|
with self.handle_grpc_errors("GetUserProfile", UserProfileServiceError):
|
|
52
|
-
# mission_id
|
|
53
|
-
request = user_profile_pb2.GetUserProfileRequest(
|
|
52
|
+
# mission_id typically contains user context
|
|
53
|
+
request = user_profile_pb2.GetUserProfileRequest(user_id=self.mission_id)
|
|
54
54
|
response = self.exec_grpc_query("GetUserProfile", request)
|
|
55
55
|
|
|
56
56
|
if not response.success:
|