digitalkin 0.3.1.dev2__py3-none-any.whl → 0.3.2a3__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 +7 -6
- 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 +29 -7
- digitalkin/core/task_manager/task_executor.py +46 -12
- digitalkin/core/task_manager/task_session.py +132 -102
- digitalkin/grpc_servers/module_server.py +95 -171
- digitalkin/grpc_servers/module_servicer.py +121 -19
- 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 +28 -392
- 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 +188 -63
- 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 +15 -3
- digitalkin/utils/conditional_schema.py +260 -0
- digitalkin/utils/dynamic_schema.py +4 -0
- digitalkin/utils/schema_splitter.py +290 -0
- {digitalkin-0.3.1.dev2.dist-info → digitalkin-0.3.2a3.dist-info}/METADATA +12 -12
- digitalkin-0.3.2a3.dist-info/RECORD +144 -0
- {digitalkin-0.3.1.dev2.dist-info → digitalkin-0.3.2a3.dist-info}/WHEEL +1 -1
- {digitalkin-0.3.1.dev2.dist-info → digitalkin-0.3.2a3.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 +5 -29
- 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.dev2.dist-info/RECORD +0 -119
- {digitalkin-0.3.1.dev2.dist-info → digitalkin-0.3.2a3.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,7 +1,15 @@
|
|
|
1
1
|
"""General utils folder."""
|
|
2
2
|
|
|
3
|
+
from digitalkin.utils.conditional_schema import (
|
|
4
|
+
Conditional,
|
|
5
|
+
ConditionalField,
|
|
6
|
+
ConditionalSchemaMixin,
|
|
7
|
+
get_conditional_metadata,
|
|
8
|
+
has_conditional,
|
|
9
|
+
)
|
|
3
10
|
from digitalkin.utils.dynamic_schema import (
|
|
4
11
|
DEFAULT_TIMEOUT,
|
|
12
|
+
Dynamic,
|
|
5
13
|
DynamicField,
|
|
6
14
|
Fetcher,
|
|
7
15
|
ResolveResult,
|
|
@@ -12,17 +20,21 @@ from digitalkin.utils.dynamic_schema import (
|
|
|
12
20
|
resolve_safe,
|
|
13
21
|
)
|
|
14
22
|
|
|
15
|
-
# Alias for cleaner API: `Dynamic` is shorter than `DynamicField`
|
|
16
|
-
Dynamic = DynamicField
|
|
17
|
-
|
|
18
23
|
__all__ = [
|
|
24
|
+
# Dynamic schema
|
|
19
25
|
"DEFAULT_TIMEOUT",
|
|
26
|
+
# Conditional schema
|
|
27
|
+
"Conditional",
|
|
28
|
+
"ConditionalField",
|
|
29
|
+
"ConditionalSchemaMixin",
|
|
20
30
|
"Dynamic",
|
|
21
31
|
"DynamicField",
|
|
22
32
|
"Fetcher",
|
|
23
33
|
"ResolveResult",
|
|
34
|
+
"get_conditional_metadata",
|
|
24
35
|
"get_dynamic_metadata",
|
|
25
36
|
"get_fetchers",
|
|
37
|
+
"has_conditional",
|
|
26
38
|
"has_dynamic",
|
|
27
39
|
"resolve",
|
|
28
40
|
"resolve_safe",
|
|
@@ -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
|
|
@@ -136,6 +136,10 @@ class DynamicField:
|
|
|
136
136
|
return hash(tuple(sorted(self.fetchers.keys())))
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
# Alias for cleaner API: `Dynamic` is shorter than `DynamicField`
|
|
140
|
+
Dynamic = DynamicField
|
|
141
|
+
|
|
142
|
+
|
|
139
143
|
def get_dynamic_metadata(field_info: FieldInfo) -> DynamicField | None:
|
|
140
144
|
"""Extract DynamicField metadata from a FieldInfo's metadata list.
|
|
141
145
|
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""Schema splitter for react-jsonschema-form."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SchemaSplitter:
|
|
7
|
+
"""Splits a combined JSON schema into jsonschema and uischema for react-jsonschema-form."""
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
def split(cls, combined_schema: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
11
|
+
"""Split schema into (jsonschema, uischema).
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
combined_schema: Combined JSON schema with ui:* properties.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Tuple of (jsonschema, uischema).
|
|
18
|
+
"""
|
|
19
|
+
defs_ui: dict[str, dict[str, Any]] = {}
|
|
20
|
+
schema_defs = combined_schema.get("$defs", {})
|
|
21
|
+
if schema_defs:
|
|
22
|
+
for def_name, def_value in schema_defs.items():
|
|
23
|
+
if isinstance(def_value, dict):
|
|
24
|
+
defs_ui[def_name] = {}
|
|
25
|
+
cls._extract_ui_properties(def_value, defs_ui[def_name], schema_defs)
|
|
26
|
+
|
|
27
|
+
json_schema: dict[str, Any] = {}
|
|
28
|
+
ui_schema: dict[str, Any] = {}
|
|
29
|
+
cls._process_object(combined_schema, json_schema, ui_schema, defs_ui, schema_defs)
|
|
30
|
+
return json_schema, ui_schema
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def _extract_ui_properties( # noqa: C901, PLR0912
|
|
34
|
+
cls,
|
|
35
|
+
source: dict[str, Any],
|
|
36
|
+
ui_target: dict[str, Any],
|
|
37
|
+
defs: dict[str, Any] | None = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Extract ui:* properties from source into ui_target recursively.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
source: Source dict to extract from.
|
|
43
|
+
ui_target: Target dict for ui properties.
|
|
44
|
+
defs: The $defs dictionary to resolve $ref references.
|
|
45
|
+
"""
|
|
46
|
+
for key, value in source.items():
|
|
47
|
+
if key.startswith("ui:"):
|
|
48
|
+
ui_target[key] = value
|
|
49
|
+
elif key == "properties" and isinstance(value, dict):
|
|
50
|
+
for prop_name, prop_value in value.items():
|
|
51
|
+
if isinstance(prop_value, dict):
|
|
52
|
+
prop_ui: dict[str, Any] = {}
|
|
53
|
+
cls._extract_ui_properties(prop_value, prop_ui, defs)
|
|
54
|
+
if prop_ui:
|
|
55
|
+
ui_target[prop_name] = prop_ui
|
|
56
|
+
elif key == "items" and isinstance(value, dict):
|
|
57
|
+
items_ui: dict[str, Any] = {}
|
|
58
|
+
cls._extract_ui_properties(value, items_ui, defs)
|
|
59
|
+
if items_ui:
|
|
60
|
+
ui_target["items"] = items_ui
|
|
61
|
+
elif key in {"allOf", "oneOf", "anyOf"} and isinstance(value, list):
|
|
62
|
+
# For oneOf/anyOf, create a mirrored structure in ui_target
|
|
63
|
+
if key in {"oneOf", "anyOf"}:
|
|
64
|
+
ui_target[key] = []
|
|
65
|
+
for item in value:
|
|
66
|
+
if isinstance(item, dict):
|
|
67
|
+
item_ui: dict[str, Any] = {}
|
|
68
|
+
cls._extract_ui_properties(item, item_ui, defs)
|
|
69
|
+
ui_target[key].append(item_ui)
|
|
70
|
+
else: # allOf - merge into parent
|
|
71
|
+
for item in value:
|
|
72
|
+
if isinstance(item, dict):
|
|
73
|
+
cls._extract_ui_properties(item, ui_target, defs)
|
|
74
|
+
elif key in {"if", "then", "else"} and isinstance(value, dict):
|
|
75
|
+
cls._extract_ui_properties(value, ui_target, defs)
|
|
76
|
+
elif key == "$ref" and isinstance(value, str) and defs is not None and value.startswith("#/$defs/"):
|
|
77
|
+
# Resolve $ref and extract UI properties from the referenced definition
|
|
78
|
+
def_name = value[8:]
|
|
79
|
+
if def_name in defs:
|
|
80
|
+
cls._extract_ui_properties(defs[def_name], ui_target, defs)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def _process_object( # noqa: C901, PLR0912, PLR0915
|
|
84
|
+
cls,
|
|
85
|
+
source: dict[str, Any],
|
|
86
|
+
json_target: dict[str, Any],
|
|
87
|
+
ui_target: dict[str, Any],
|
|
88
|
+
defs_ui: dict[str, dict[str, Any]],
|
|
89
|
+
schema_defs: dict[str, Any] | None = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Process an object, splitting json and ui properties.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
source: Source object to process.
|
|
95
|
+
json_target: Target dict for json schema.
|
|
96
|
+
ui_target: Target dict for ui schema.
|
|
97
|
+
defs_ui: Pre-extracted UI properties from $defs.
|
|
98
|
+
schema_defs: The original $defs dictionary to resolve $ref in _extract_ui_properties.
|
|
99
|
+
"""
|
|
100
|
+
for key, value in source.items():
|
|
101
|
+
if key.startswith("ui:"):
|
|
102
|
+
ui_target[key] = value
|
|
103
|
+
elif key == "properties" and isinstance(value, dict):
|
|
104
|
+
json_target["properties"] = {}
|
|
105
|
+
for prop_name, prop_value in value.items():
|
|
106
|
+
if isinstance(prop_value, dict):
|
|
107
|
+
# Skip hidden fields
|
|
108
|
+
if prop_value.get("hidden") is True:
|
|
109
|
+
continue
|
|
110
|
+
json_target["properties"][prop_name] = {}
|
|
111
|
+
prop_ui: dict[str, Any] = {}
|
|
112
|
+
cls._process_property(prop_value, json_target["properties"][prop_name], prop_ui, defs_ui)
|
|
113
|
+
if prop_ui:
|
|
114
|
+
ui_target[prop_name] = prop_ui
|
|
115
|
+
else:
|
|
116
|
+
json_target["properties"][prop_name] = prop_value
|
|
117
|
+
elif key == "$defs" and isinstance(value, dict):
|
|
118
|
+
json_target["$defs"] = {}
|
|
119
|
+
for def_name, def_value in value.items():
|
|
120
|
+
if isinstance(def_value, dict):
|
|
121
|
+
json_target["$defs"][def_name] = {}
|
|
122
|
+
cls._strip_ui_properties(def_value, json_target["$defs"][def_name])
|
|
123
|
+
else:
|
|
124
|
+
json_target["$defs"][def_name] = def_value
|
|
125
|
+
elif key == "items" and isinstance(value, dict):
|
|
126
|
+
json_target["items"] = {}
|
|
127
|
+
items_ui: dict[str, Any] = {}
|
|
128
|
+
cls._process_property(value, json_target["items"], items_ui, defs_ui)
|
|
129
|
+
if items_ui:
|
|
130
|
+
ui_target["items"] = items_ui
|
|
131
|
+
elif key == "allOf" and isinstance(value, list):
|
|
132
|
+
json_target["allOf"] = []
|
|
133
|
+
for item in value:
|
|
134
|
+
if isinstance(item, dict):
|
|
135
|
+
item_json_all_of: dict[str, Any] = {}
|
|
136
|
+
cls._strip_ui_properties(item, item_json_all_of)
|
|
137
|
+
json_target["allOf"].append(item_json_all_of)
|
|
138
|
+
# Extract UI properties from allOf item
|
|
139
|
+
cls._extract_ui_properties(item, ui_target, schema_defs)
|
|
140
|
+
else:
|
|
141
|
+
json_target["allOf"].append(item)
|
|
142
|
+
elif key in {"oneOf", "anyOf"} and isinstance(value, list):
|
|
143
|
+
json_target[key] = []
|
|
144
|
+
ui_target[key] = [] # Mirror structure in ui_schema
|
|
145
|
+
for item in value:
|
|
146
|
+
if isinstance(item, dict):
|
|
147
|
+
item_json_oneof_anyof: dict[str, Any] = {}
|
|
148
|
+
cls._strip_ui_properties(item, item_json_oneof_anyof)
|
|
149
|
+
json_target[key].append(item_json_oneof_anyof)
|
|
150
|
+
# Extract UI properties into a separate dict for this variant
|
|
151
|
+
item_ui_oneof_anyof: dict[str, Any] = {}
|
|
152
|
+
cls._extract_ui_properties(item, item_ui_oneof_anyof, schema_defs)
|
|
153
|
+
ui_target[key].append(item_ui_oneof_anyof)
|
|
154
|
+
else:
|
|
155
|
+
json_target[key].append(item)
|
|
156
|
+
elif key in {"if", "then", "else"} and isinstance(value, dict):
|
|
157
|
+
json_target[key] = {}
|
|
158
|
+
cls._strip_ui_properties(value, json_target[key])
|
|
159
|
+
# Extract UI properties from conditional
|
|
160
|
+
cls._extract_ui_properties(value, ui_target, schema_defs)
|
|
161
|
+
elif key == "hidden":
|
|
162
|
+
# Strip hidden key from json schema
|
|
163
|
+
continue
|
|
164
|
+
else:
|
|
165
|
+
json_target[key] = value
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def _process_property( # noqa: C901, PLR0912
|
|
169
|
+
cls,
|
|
170
|
+
source: dict[str, Any],
|
|
171
|
+
json_target: dict[str, Any],
|
|
172
|
+
ui_target: dict[str, Any],
|
|
173
|
+
defs_ui: dict[str, dict[str, Any]],
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Process a property, resolving $ref for UI properties.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
source: Source property dict.
|
|
179
|
+
json_target: Target dict for json schema.
|
|
180
|
+
ui_target: Target dict for ui schema.
|
|
181
|
+
defs_ui: Pre-extracted UI properties from $defs.
|
|
182
|
+
"""
|
|
183
|
+
if "$ref" in source:
|
|
184
|
+
ref_path = source["$ref"]
|
|
185
|
+
if ref_path.startswith("#/$defs/"):
|
|
186
|
+
def_name = ref_path[8:]
|
|
187
|
+
if def_name in defs_ui:
|
|
188
|
+
ui_target.update(defs_ui[def_name])
|
|
189
|
+
|
|
190
|
+
for key, value in source.items():
|
|
191
|
+
if key.startswith("ui:"):
|
|
192
|
+
ui_target[key] = value
|
|
193
|
+
elif key == "properties" and isinstance(value, dict):
|
|
194
|
+
json_target["properties"] = {}
|
|
195
|
+
for prop_name, prop_value in value.items():
|
|
196
|
+
if isinstance(prop_value, dict):
|
|
197
|
+
# Skip hidden fields
|
|
198
|
+
if prop_value.get("hidden") is True:
|
|
199
|
+
continue
|
|
200
|
+
json_target["properties"][prop_name] = {}
|
|
201
|
+
prop_ui: dict[str, Any] = {}
|
|
202
|
+
cls._process_property(prop_value, json_target["properties"][prop_name], prop_ui, defs_ui)
|
|
203
|
+
if prop_ui:
|
|
204
|
+
ui_target[prop_name] = prop_ui
|
|
205
|
+
else:
|
|
206
|
+
json_target["properties"][prop_name] = prop_value
|
|
207
|
+
elif key == "items" and isinstance(value, dict):
|
|
208
|
+
json_target["items"] = {}
|
|
209
|
+
items_ui: dict[str, Any] = {}
|
|
210
|
+
cls._process_property(value, json_target["items"], items_ui, defs_ui)
|
|
211
|
+
if items_ui:
|
|
212
|
+
ui_target["items"] = items_ui
|
|
213
|
+
elif key in {"oneOf", "anyOf"} and isinstance(value, list):
|
|
214
|
+
json_target[key] = []
|
|
215
|
+
ui_target[key] = [] # Mirror structure in ui_schema
|
|
216
|
+
for item in value:
|
|
217
|
+
if isinstance(item, dict):
|
|
218
|
+
item_json_oneof_anyof: dict[str, Any] = {}
|
|
219
|
+
cls._strip_ui_properties(item, item_json_oneof_anyof)
|
|
220
|
+
json_target[key].append(item_json_oneof_anyof)
|
|
221
|
+
# Extract UI properties for this variant
|
|
222
|
+
item_ui_oneof_anyof: dict[str, Any] = {}
|
|
223
|
+
ref_path = item.get("$ref", "")
|
|
224
|
+
if ref_path.startswith("#/$defs/") and ref_path[8:] in defs_ui:
|
|
225
|
+
item_ui_oneof_anyof.update(defs_ui[ref_path[8:]])
|
|
226
|
+
ui_target[key].append(item_ui_oneof_anyof)
|
|
227
|
+
else:
|
|
228
|
+
json_target[key].append(item)
|
|
229
|
+
elif key == "hidden":
|
|
230
|
+
# Strip hidden key from json schema
|
|
231
|
+
continue
|
|
232
|
+
else:
|
|
233
|
+
json_target[key] = value
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def _strip_ui_properties(cls, source: dict[str, Any], json_target: dict[str, Any]) -> None: # noqa: C901, PLR0912
|
|
237
|
+
"""Copy source to json_target, stripping ui:* properties.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
source: Source dict.
|
|
241
|
+
json_target: Target dict without ui:* properties.
|
|
242
|
+
"""
|
|
243
|
+
for key, value in source.items():
|
|
244
|
+
if key.startswith("ui:") or key == "hidden":
|
|
245
|
+
continue
|
|
246
|
+
if key == "properties" and isinstance(value, dict):
|
|
247
|
+
json_target["properties"] = {}
|
|
248
|
+
for prop_name, prop_value in value.items():
|
|
249
|
+
if isinstance(prop_value, dict):
|
|
250
|
+
# Skip hidden fields
|
|
251
|
+
if prop_value.get("hidden") is True:
|
|
252
|
+
continue
|
|
253
|
+
json_target["properties"][prop_name] = {}
|
|
254
|
+
cls._strip_ui_properties(prop_value, json_target["properties"][prop_name])
|
|
255
|
+
else:
|
|
256
|
+
json_target["properties"][prop_name] = prop_value
|
|
257
|
+
elif key == "$defs" and isinstance(value, dict):
|
|
258
|
+
json_target["$defs"] = {}
|
|
259
|
+
for def_name, def_value in value.items():
|
|
260
|
+
if isinstance(def_value, dict):
|
|
261
|
+
json_target["$defs"][def_name] = {}
|
|
262
|
+
cls._strip_ui_properties(def_value, json_target["$defs"][def_name])
|
|
263
|
+
else:
|
|
264
|
+
json_target["$defs"][def_name] = def_value
|
|
265
|
+
elif key == "items" and isinstance(value, dict):
|
|
266
|
+
json_target["items"] = {}
|
|
267
|
+
cls._strip_ui_properties(value, json_target["items"])
|
|
268
|
+
elif key == "allOf" and isinstance(value, list):
|
|
269
|
+
json_target["allOf"] = []
|
|
270
|
+
for item in value:
|
|
271
|
+
if isinstance(item, dict):
|
|
272
|
+
item_json_all_of: dict[str, Any] = {}
|
|
273
|
+
cls._strip_ui_properties(item, item_json_all_of)
|
|
274
|
+
json_target["allOf"].append(item_json_all_of)
|
|
275
|
+
else:
|
|
276
|
+
json_target["allOf"].append(item)
|
|
277
|
+
elif key in {"oneOf", "anyOf"} and isinstance(value, list):
|
|
278
|
+
json_target[key] = []
|
|
279
|
+
for item in value:
|
|
280
|
+
if isinstance(item, dict):
|
|
281
|
+
item_json_oneof_anyof: dict[str, Any] = {}
|
|
282
|
+
cls._strip_ui_properties(item, item_json_oneof_anyof)
|
|
283
|
+
json_target[key].append(item_json_oneof_anyof)
|
|
284
|
+
else:
|
|
285
|
+
json_target[key].append(item)
|
|
286
|
+
elif key in {"if", "then", "else"} and isinstance(value, dict):
|
|
287
|
+
json_target[key] = {}
|
|
288
|
+
cls._strip_ui_properties(value, json_target[key])
|
|
289
|
+
else:
|
|
290
|
+
json_target[key] = value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: digitalkin
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2a3
|
|
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
|
|
@@ -434,35 +434,35 @@ License: Attribution-NonCommercial-ShareAlike 4.0 International
|
|
|
434
434
|
Creative Commons may be contacted at creativecommons.org.
|
|
435
435
|
https://creativecommons.org/licenses/by-nc-sa/4.0/
|
|
436
436
|
|
|
437
|
-
Project-URL: Homepage, https://github.com/DigitalKin-ai/digitalkin
|
|
438
437
|
Project-URL: Documentation, https://github.com/DigitalKin-ai/digitalkin
|
|
438
|
+
Project-URL: Homepage, https://github.com/DigitalKin-ai/digitalkin
|
|
439
439
|
Project-URL: Issues, https://github.com/DigitalKin-ai/digitalkin/issues
|
|
440
|
-
Keywords: digitalkin,kin,
|
|
440
|
+
Keywords: agent,digitalkin,gprc,kin,sdk
|
|
441
441
|
Classifier: Development Status :: 3 - Alpha
|
|
442
442
|
Classifier: Intended Audience :: Developers
|
|
443
|
+
Classifier: License :: Other/Proprietary License
|
|
443
444
|
Classifier: Operating System :: OS Independent
|
|
444
|
-
Classifier:
|
|
445
|
-
Classifier: Programming Language :: Python
|
|
445
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
446
446
|
Classifier: Programming Language :: Python :: 3.10
|
|
447
447
|
Classifier: Programming Language :: Python :: 3.11
|
|
448
448
|
Classifier: Programming Language :: Python :: 3.12
|
|
449
449
|
Classifier: Programming Language :: Python :: 3.13
|
|
450
|
-
Classifier: Programming Language :: Python
|
|
451
|
-
Classifier:
|
|
450
|
+
Classifier: Programming Language :: Python
|
|
451
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
452
452
|
Requires-Python: >=3.10
|
|
453
453
|
Description-Content-Type: text/markdown
|
|
454
454
|
License-File: LICENSE
|
|
455
|
-
Requires-Dist:
|
|
455
|
+
Requires-Dist: agentic-mesh-protocol==0.2.1.dev1
|
|
456
456
|
Requires-Dist: grpcio-health-checking>=1.76.0
|
|
457
457
|
Requires-Dist: grpcio-reflection>=1.76.0
|
|
458
458
|
Requires-Dist: grpcio-status>=1.76.0
|
|
459
459
|
Requires-Dist: pydantic>=2.12.5
|
|
460
|
-
Requires-Dist: surrealdb>=1.0.
|
|
460
|
+
Requires-Dist: surrealdb>=1.0.7
|
|
461
461
|
Provides-Extra: taskiq
|
|
462
|
-
Requires-Dist: rstream>=0.40.
|
|
462
|
+
Requires-Dist: rstream>=0.40.1; extra == "taskiq"
|
|
463
463
|
Requires-Dist: taskiq-aio-pika>=0.5.0; extra == "taskiq"
|
|
464
|
-
Requires-Dist: taskiq-redis>=1.
|
|
465
|
-
Requires-Dist: taskiq[reload]>=0.12.
|
|
464
|
+
Requires-Dist: taskiq-redis>=1.2.0; extra == "taskiq"
|
|
465
|
+
Requires-Dist: taskiq[reload]>=0.12.1; extra == "taskiq"
|
|
466
466
|
Dynamic: license-file
|
|
467
467
|
|
|
468
468
|
# DigitalKin Python SDK
|