digitalkin 0.3.1.dev1__py3-none-any.whl → 0.3.2a2__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/server_async_insecure.py +6 -5
- base_server/server_async_secure.py +6 -5
- base_server/server_sync_insecure.py +5 -4
- base_server/server_sync_secure.py +5 -4
- digitalkin/__version__.py +1 -1
- digitalkin/core/job_manager/base_job_manager.py +1 -1
- digitalkin/core/job_manager/single_job_manager.py +78 -36
- digitalkin/core/job_manager/taskiq_broker.py +8 -7
- digitalkin/core/job_manager/taskiq_job_manager.py +9 -5
- digitalkin/core/task_manager/base_task_manager.py +3 -1
- digitalkin/core/task_manager/surrealdb_repository.py +13 -7
- digitalkin/core/task_manager/task_executor.py +27 -10
- digitalkin/core/task_manager/task_session.py +133 -101
- digitalkin/grpc_servers/module_server.py +95 -171
- digitalkin/grpc_servers/module_servicer.py +133 -27
- digitalkin/grpc_servers/utils/grpc_client_wrapper.py +36 -10
- digitalkin/grpc_servers/utils/utility_schema_extender.py +106 -0
- digitalkin/models/__init__.py +1 -1
- digitalkin/models/core/job_manager_models.py +0 -8
- digitalkin/models/core/task_monitor.py +23 -1
- digitalkin/models/grpc_servers/models.py +95 -8
- digitalkin/models/module/__init__.py +26 -13
- digitalkin/models/module/base_types.py +61 -0
- digitalkin/models/module/module_context.py +279 -13
- digitalkin/models/module/module_types.py +29 -109
- digitalkin/models/module/setup_types.py +547 -0
- digitalkin/models/module/tool_cache.py +230 -0
- digitalkin/models/module/tool_reference.py +160 -0
- digitalkin/models/module/utility.py +167 -0
- digitalkin/models/services/cost.py +22 -1
- digitalkin/models/services/registry.py +77 -0
- digitalkin/modules/__init__.py +5 -1
- digitalkin/modules/_base_module.py +253 -90
- digitalkin/modules/archetype_module.py +6 -1
- digitalkin/modules/tool_module.py +6 -1
- digitalkin/modules/triggers/__init__.py +8 -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/services/__init__.py +4 -0
- digitalkin/services/communication/__init__.py +7 -0
- digitalkin/services/communication/communication_strategy.py +87 -0
- digitalkin/services/communication/default_communication.py +104 -0
- digitalkin/services/communication/grpc_communication.py +264 -0
- digitalkin/services/cost/cost_strategy.py +36 -14
- digitalkin/services/cost/default_cost.py +61 -1
- digitalkin/services/cost/grpc_cost.py +98 -2
- digitalkin/services/filesystem/grpc_filesystem.py +9 -2
- digitalkin/services/registry/__init__.py +22 -1
- digitalkin/services/registry/default_registry.py +156 -4
- digitalkin/services/registry/exceptions.py +47 -0
- digitalkin/services/registry/grpc_registry.py +382 -0
- digitalkin/services/registry/registry_models.py +15 -0
- digitalkin/services/registry/registry_strategy.py +106 -4
- digitalkin/services/services_config.py +25 -3
- digitalkin/services/services_models.py +5 -1
- digitalkin/services/setup/default_setup.py +1 -1
- digitalkin/services/setup/grpc_setup.py +1 -1
- digitalkin/services/storage/grpc_storage.py +1 -1
- digitalkin/services/user_profile/__init__.py +11 -0
- digitalkin/services/user_profile/grpc_user_profile.py +2 -2
- digitalkin/services/user_profile/user_profile_strategy.py +0 -15
- digitalkin/utils/__init__.py +40 -0
- digitalkin/utils/conditional_schema.py +260 -0
- digitalkin/utils/dynamic_schema.py +487 -0
- digitalkin/utils/schema_splitter.py +290 -0
- {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/METADATA +13 -13
- digitalkin-0.3.2a2.dist-info/RECORD +144 -0
- {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/WHEEL +1 -1
- {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/top_level.txt +1 -0
- modules/archetype_with_tools_module.py +232 -0
- modules/cpu_intensive_module.py +1 -1
- modules/dynamic_setup_module.py +338 -0
- modules/minimal_llm_module.py +1 -1
- modules/text_transform_module.py +1 -1
- monitoring/digitalkin_observability/__init__.py +46 -0
- monitoring/digitalkin_observability/http_server.py +150 -0
- monitoring/digitalkin_observability/interceptors.py +176 -0
- monitoring/digitalkin_observability/metrics.py +201 -0
- monitoring/digitalkin_observability/prometheus.py +137 -0
- monitoring/tests/test_metrics.py +172 -0
- services/filesystem_module.py +7 -5
- services/storage_module.py +4 -2
- digitalkin/grpc_servers/registry_server.py +0 -65
- digitalkin/grpc_servers/registry_servicer.py +0 -456
- digitalkin-0.3.1.dev1.dist-info/RECORD +0 -117
- {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,21 +13,6 @@ class UserProfileServiceError(Exception):
|
|
|
13
13
|
class UserProfileStrategy(BaseStrategy, ABC):
|
|
14
14
|
"""Abstract base class for UserProfile strategies."""
|
|
15
15
|
|
|
16
|
-
def __init__(
|
|
17
|
-
self,
|
|
18
|
-
mission_id: str,
|
|
19
|
-
setup_id: str,
|
|
20
|
-
setup_version_id: str,
|
|
21
|
-
) -> None:
|
|
22
|
-
"""Initialize the strategy.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
mission_id: The ID of the mission this strategy is associated with
|
|
26
|
-
setup_id: The ID of the setup
|
|
27
|
-
setup_version_id: The ID of the setup version this strategy is associated with
|
|
28
|
-
"""
|
|
29
|
-
super().__init__(mission_id, setup_id, setup_version_id)
|
|
30
|
-
|
|
31
16
|
@abstractmethod
|
|
32
17
|
def get_user_profile(self) -> dict[str, Any]:
|
|
33
18
|
"""Get user profile data.
|
digitalkin/utils/__init__.py
CHANGED
|
@@ -1 +1,41 @@
|
|
|
1
1
|
"""General utils folder."""
|
|
2
|
+
|
|
3
|
+
from digitalkin.utils.conditional_schema import (
|
|
4
|
+
Conditional,
|
|
5
|
+
ConditionalField,
|
|
6
|
+
ConditionalSchemaMixin,
|
|
7
|
+
get_conditional_metadata,
|
|
8
|
+
has_conditional,
|
|
9
|
+
)
|
|
10
|
+
from digitalkin.utils.dynamic_schema import (
|
|
11
|
+
DEFAULT_TIMEOUT,
|
|
12
|
+
Dynamic,
|
|
13
|
+
DynamicField,
|
|
14
|
+
Fetcher,
|
|
15
|
+
ResolveResult,
|
|
16
|
+
get_dynamic_metadata,
|
|
17
|
+
get_fetchers,
|
|
18
|
+
has_dynamic,
|
|
19
|
+
resolve,
|
|
20
|
+
resolve_safe,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
# Dynamic schema
|
|
25
|
+
"DEFAULT_TIMEOUT",
|
|
26
|
+
# Conditional schema
|
|
27
|
+
"Conditional",
|
|
28
|
+
"ConditionalField",
|
|
29
|
+
"ConditionalSchemaMixin",
|
|
30
|
+
"Dynamic",
|
|
31
|
+
"DynamicField",
|
|
32
|
+
"Fetcher",
|
|
33
|
+
"ResolveResult",
|
|
34
|
+
"get_conditional_metadata",
|
|
35
|
+
"get_dynamic_metadata",
|
|
36
|
+
"get_fetchers",
|
|
37
|
+
"has_conditional",
|
|
38
|
+
"has_dynamic",
|
|
39
|
+
"resolve",
|
|
40
|
+
"resolve_safe",
|
|
41
|
+
]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Conditional field visibility for react-jsonschema-form.
|
|
2
|
+
|
|
3
|
+
This module provides a clean way to mark fields as conditional using Annotated metadata,
|
|
4
|
+
generating JSON Schema with if/then clauses for react-jsonschema-form.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
from typing import Annotated, Literal
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
from digitalkin.utils import Conditional, ConditionalSchemaMixin
|
|
10
|
+
|
|
11
|
+
class Tools(ConditionalSchemaMixin, BaseModel):
|
|
12
|
+
web_search_enabled: bool = Field(...)
|
|
13
|
+
|
|
14
|
+
web_search_engine: Annotated[
|
|
15
|
+
Literal["duckduckgo", "tavily"],
|
|
16
|
+
Conditional(trigger="web_search_enabled", show_when=True),
|
|
17
|
+
] = Field(...)
|
|
18
|
+
|
|
19
|
+
See Also:
|
|
20
|
+
- Documentation: docs/api/conditional_schema.md
|
|
21
|
+
- Tests: tests/utils/test_conditional_schema.py
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from pydantic.annotated_handlers import GetJsonSchemaHandler
|
|
33
|
+
from pydantic.fields import FieldInfo
|
|
34
|
+
from pydantic.json_schema import JsonSchemaValue
|
|
35
|
+
from pydantic_core.core_schema import CoreSchema
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class ConditionalField:
|
|
40
|
+
"""Metadata for conditional field visibility.
|
|
41
|
+
|
|
42
|
+
Use with typing.Annotated to mark fields that should only appear
|
|
43
|
+
when a trigger field has a specific value.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
trigger: Name of the field that controls visibility.
|
|
47
|
+
show_when: Value(s) that trigger field must have to show this field.
|
|
48
|
+
Can be a boolean, string, or list of strings for multiple values.
|
|
49
|
+
required_when_shown: Whether field is required when visible. Defaults to True.
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
# Boolean condition
|
|
53
|
+
web_search_engine: Annotated[
|
|
54
|
+
str,
|
|
55
|
+
Conditional(trigger="web_search_enabled", show_when=True),
|
|
56
|
+
] = Field(...)
|
|
57
|
+
|
|
58
|
+
# Enum condition
|
|
59
|
+
advanced_option: Annotated[
|
|
60
|
+
str,
|
|
61
|
+
Conditional(trigger="mode", show_when="advanced"),
|
|
62
|
+
] = Field(...)
|
|
63
|
+
|
|
64
|
+
# Multiple values condition
|
|
65
|
+
shared_feature: Annotated[
|
|
66
|
+
bool,
|
|
67
|
+
Conditional(trigger="mode", show_when=["standard", "advanced"]),
|
|
68
|
+
] = Field(...)
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
trigger: str
|
|
72
|
+
show_when: bool | str | list[str]
|
|
73
|
+
required_when_shown: bool = True
|
|
74
|
+
|
|
75
|
+
def __post_init__(self) -> None:
|
|
76
|
+
"""Normalize single-item lists to scalar values."""
|
|
77
|
+
if isinstance(self.show_when, list) and len(self.show_when) == 1:
|
|
78
|
+
self.show_when = self.show_when[0]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Short alias for cleaner API
|
|
82
|
+
Conditional = ConditionalField
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_conditional_metadata(field_info: FieldInfo) -> ConditionalField | None:
|
|
86
|
+
"""Extract ConditionalField from field metadata.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
field_info: The Pydantic FieldInfo object to inspect.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
The ConditionalField metadata instance if found, None otherwise.
|
|
93
|
+
"""
|
|
94
|
+
for meta in field_info.metadata:
|
|
95
|
+
if isinstance(meta, ConditionalField):
|
|
96
|
+
return meta
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def has_conditional(field_info: FieldInfo) -> bool:
|
|
101
|
+
"""Check if field has ConditionalField metadata.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
field_info: The Pydantic FieldInfo object to check.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
True if the field has ConditionalField metadata, False otherwise.
|
|
108
|
+
"""
|
|
109
|
+
return get_conditional_metadata(field_info) is not None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _collect_conditions(
|
|
113
|
+
model_fields: dict[str, FieldInfo],
|
|
114
|
+
props: dict[str, Any],
|
|
115
|
+
) -> tuple[dict[tuple[str, Any], list[tuple[str, bool]]], set[str]]:
|
|
116
|
+
"""Collect conditional fields grouped by trigger and show_when value.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
model_fields: The model's field definitions.
|
|
120
|
+
props: The schema properties dict.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Tuple of (conditions dict, fields to remove set).
|
|
124
|
+
"""
|
|
125
|
+
conditions: dict[tuple[str, Any], list[tuple[str, bool]]] = {}
|
|
126
|
+
fields_to_remove: set[str] = set()
|
|
127
|
+
|
|
128
|
+
for field_name, field_info in model_fields.items():
|
|
129
|
+
cond = get_conditional_metadata(field_info)
|
|
130
|
+
if cond is None or field_name not in props:
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
show_key = tuple(cond.show_when) if isinstance(cond.show_when, list) else cond.show_when
|
|
134
|
+
key = (cond.trigger, show_key)
|
|
135
|
+
|
|
136
|
+
if key not in conditions:
|
|
137
|
+
conditions[key] = []
|
|
138
|
+
conditions[key].append((field_name, cond.required_when_shown))
|
|
139
|
+
fields_to_remove.add(field_name)
|
|
140
|
+
|
|
141
|
+
return conditions, fields_to_remove
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _build_if_clause(trigger: str, *, show_when: bool | str | tuple[str, ...]) -> dict[str, Any]:
|
|
145
|
+
"""Build the if clause for a conditional.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
trigger: The trigger field name.
|
|
149
|
+
show_when: The value(s) that trigger visibility.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The if clause dict.
|
|
153
|
+
"""
|
|
154
|
+
if isinstance(show_when, tuple):
|
|
155
|
+
return {"properties": {trigger: {"enum": list(show_when)}}, "required": [trigger]}
|
|
156
|
+
return {"properties": {trigger: {"const": show_when}}, "required": [trigger]}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _resolve_field_schema(
|
|
160
|
+
field_schema: dict[str, Any],
|
|
161
|
+
handler: GetJsonSchemaHandler,
|
|
162
|
+
) -> dict[str, Any]:
|
|
163
|
+
"""Resolve $ref in field schema if present.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
field_schema: The field's schema dict.
|
|
167
|
+
handler: The JSON schema handler for resolving refs.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
The resolved schema dict.
|
|
171
|
+
"""
|
|
172
|
+
if "$ref" not in field_schema:
|
|
173
|
+
return field_schema
|
|
174
|
+
|
|
175
|
+
resolved = handler.resolve_ref_schema(field_schema)
|
|
176
|
+
extra = {k: v for k, v in field_schema.items() if k != "$ref"}
|
|
177
|
+
return {**resolved, **extra}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class ConditionalSchemaMixin(BaseModel):
|
|
181
|
+
"""Mixin for automatic conditional field processing in JSON schema.
|
|
182
|
+
|
|
183
|
+
Inherit from this mixin to automatically generate JSON Schema with
|
|
184
|
+
if/then clauses for fields marked with ConditionalField metadata.
|
|
185
|
+
|
|
186
|
+
The mixin processes Annotated fields with Conditional metadata and:
|
|
187
|
+
1. Removes conditional fields from main properties
|
|
188
|
+
2. Adds them to allOf with if/then clauses
|
|
189
|
+
3. Groups multiple fields with the same condition together
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
class Config(ConditionalSchemaMixin, BaseModel):
|
|
193
|
+
mode: Literal["basic", "advanced"] = Field(...)
|
|
194
|
+
|
|
195
|
+
advanced_option: Annotated[
|
|
196
|
+
str,
|
|
197
|
+
Conditional(trigger="mode", show_when="advanced"),
|
|
198
|
+
] = Field(...)
|
|
199
|
+
|
|
200
|
+
# Generates schema with:
|
|
201
|
+
# {
|
|
202
|
+
# "properties": {"mode": {...}},
|
|
203
|
+
# "allOf": [{
|
|
204
|
+
# "if": {"properties": {"mode": {"const": "advanced"}}},
|
|
205
|
+
# "then": {"properties": {"advanced_option": {...}}}
|
|
206
|
+
# }]
|
|
207
|
+
# }
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
model_fields: ClassVar[dict[str, FieldInfo]] # type: ignore[misc]
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def __get_pydantic_json_schema__( # noqa: PLW3201
|
|
214
|
+
cls,
|
|
215
|
+
core_schema: CoreSchema,
|
|
216
|
+
handler: GetJsonSchemaHandler,
|
|
217
|
+
) -> JsonSchemaValue:
|
|
218
|
+
"""Generate JSON schema with conditional field handling.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
core_schema: The Pydantic core schema.
|
|
222
|
+
handler: The JSON schema handler for resolving refs.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
The JSON schema with if/then clauses for conditional fields.
|
|
226
|
+
"""
|
|
227
|
+
schema = handler(core_schema)
|
|
228
|
+
props = schema.get("properties", {})
|
|
229
|
+
if not props:
|
|
230
|
+
return schema
|
|
231
|
+
|
|
232
|
+
conditions, fields_to_remove = _collect_conditions(cls.model_fields, props)
|
|
233
|
+
if not conditions:
|
|
234
|
+
return schema
|
|
235
|
+
|
|
236
|
+
all_of = schema.setdefault("allOf", [])
|
|
237
|
+
|
|
238
|
+
for (trigger, show_when), field_list in conditions.items():
|
|
239
|
+
then_props: dict[str, Any] = {}
|
|
240
|
+
then_required: list[str] = []
|
|
241
|
+
|
|
242
|
+
for field_name, required in field_list:
|
|
243
|
+
then_props[field_name] = _resolve_field_schema(props[field_name], handler)
|
|
244
|
+
if required:
|
|
245
|
+
then_required.append(field_name)
|
|
246
|
+
|
|
247
|
+
if_clause = _build_if_clause(trigger, show_when=show_when)
|
|
248
|
+
then_clause: dict[str, Any] = {"properties": then_props}
|
|
249
|
+
if then_required:
|
|
250
|
+
then_clause["required"] = then_required
|
|
251
|
+
|
|
252
|
+
all_of.append({"if": if_clause, "then": then_clause})
|
|
253
|
+
|
|
254
|
+
for field_name in fields_to_remove:
|
|
255
|
+
del props[field_name]
|
|
256
|
+
|
|
257
|
+
if "required" in schema:
|
|
258
|
+
schema["required"] = [r for r in schema["required"] if r not in fields_to_remove]
|
|
259
|
+
|
|
260
|
+
return schema
|