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.
Files changed (87) hide show
  1. base_server/server_async_insecure.py +6 -5
  2. base_server/server_async_secure.py +6 -5
  3. base_server/server_sync_insecure.py +5 -4
  4. base_server/server_sync_secure.py +5 -4
  5. digitalkin/__version__.py +1 -1
  6. digitalkin/core/job_manager/base_job_manager.py +1 -1
  7. digitalkin/core/job_manager/single_job_manager.py +78 -36
  8. digitalkin/core/job_manager/taskiq_broker.py +8 -7
  9. digitalkin/core/job_manager/taskiq_job_manager.py +9 -5
  10. digitalkin/core/task_manager/base_task_manager.py +3 -1
  11. digitalkin/core/task_manager/surrealdb_repository.py +13 -7
  12. digitalkin/core/task_manager/task_executor.py +27 -10
  13. digitalkin/core/task_manager/task_session.py +133 -101
  14. digitalkin/grpc_servers/module_server.py +95 -171
  15. digitalkin/grpc_servers/module_servicer.py +133 -27
  16. digitalkin/grpc_servers/utils/grpc_client_wrapper.py +36 -10
  17. digitalkin/grpc_servers/utils/utility_schema_extender.py +106 -0
  18. digitalkin/models/__init__.py +1 -1
  19. digitalkin/models/core/job_manager_models.py +0 -8
  20. digitalkin/models/core/task_monitor.py +23 -1
  21. digitalkin/models/grpc_servers/models.py +95 -8
  22. digitalkin/models/module/__init__.py +26 -13
  23. digitalkin/models/module/base_types.py +61 -0
  24. digitalkin/models/module/module_context.py +279 -13
  25. digitalkin/models/module/module_types.py +29 -109
  26. digitalkin/models/module/setup_types.py +547 -0
  27. digitalkin/models/module/tool_cache.py +230 -0
  28. digitalkin/models/module/tool_reference.py +160 -0
  29. digitalkin/models/module/utility.py +167 -0
  30. digitalkin/models/services/cost.py +22 -1
  31. digitalkin/models/services/registry.py +77 -0
  32. digitalkin/modules/__init__.py +5 -1
  33. digitalkin/modules/_base_module.py +253 -90
  34. digitalkin/modules/archetype_module.py +6 -1
  35. digitalkin/modules/tool_module.py +6 -1
  36. digitalkin/modules/triggers/__init__.py +8 -0
  37. digitalkin/modules/triggers/healthcheck_ping_trigger.py +45 -0
  38. digitalkin/modules/triggers/healthcheck_services_trigger.py +63 -0
  39. digitalkin/modules/triggers/healthcheck_status_trigger.py +52 -0
  40. digitalkin/services/__init__.py +4 -0
  41. digitalkin/services/communication/__init__.py +7 -0
  42. digitalkin/services/communication/communication_strategy.py +87 -0
  43. digitalkin/services/communication/default_communication.py +104 -0
  44. digitalkin/services/communication/grpc_communication.py +264 -0
  45. digitalkin/services/cost/cost_strategy.py +36 -14
  46. digitalkin/services/cost/default_cost.py +61 -1
  47. digitalkin/services/cost/grpc_cost.py +98 -2
  48. digitalkin/services/filesystem/grpc_filesystem.py +9 -2
  49. digitalkin/services/registry/__init__.py +22 -1
  50. digitalkin/services/registry/default_registry.py +156 -4
  51. digitalkin/services/registry/exceptions.py +47 -0
  52. digitalkin/services/registry/grpc_registry.py +382 -0
  53. digitalkin/services/registry/registry_models.py +15 -0
  54. digitalkin/services/registry/registry_strategy.py +106 -4
  55. digitalkin/services/services_config.py +25 -3
  56. digitalkin/services/services_models.py +5 -1
  57. digitalkin/services/setup/default_setup.py +1 -1
  58. digitalkin/services/setup/grpc_setup.py +1 -1
  59. digitalkin/services/storage/grpc_storage.py +1 -1
  60. digitalkin/services/user_profile/__init__.py +11 -0
  61. digitalkin/services/user_profile/grpc_user_profile.py +2 -2
  62. digitalkin/services/user_profile/user_profile_strategy.py +0 -15
  63. digitalkin/utils/__init__.py +40 -0
  64. digitalkin/utils/conditional_schema.py +260 -0
  65. digitalkin/utils/dynamic_schema.py +487 -0
  66. digitalkin/utils/schema_splitter.py +290 -0
  67. {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/METADATA +13 -13
  68. digitalkin-0.3.2a2.dist-info/RECORD +144 -0
  69. {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/WHEEL +1 -1
  70. {digitalkin-0.3.1.dev1.dist-info → digitalkin-0.3.2a2.dist-info}/top_level.txt +1 -0
  71. modules/archetype_with_tools_module.py +232 -0
  72. modules/cpu_intensive_module.py +1 -1
  73. modules/dynamic_setup_module.py +338 -0
  74. modules/minimal_llm_module.py +1 -1
  75. modules/text_transform_module.py +1 -1
  76. monitoring/digitalkin_observability/__init__.py +46 -0
  77. monitoring/digitalkin_observability/http_server.py +150 -0
  78. monitoring/digitalkin_observability/interceptors.py +176 -0
  79. monitoring/digitalkin_observability/metrics.py +201 -0
  80. monitoring/digitalkin_observability/prometheus.py +137 -0
  81. monitoring/tests/test_metrics.py +172 -0
  82. services/filesystem_module.py +7 -5
  83. services/storage_module.py +4 -2
  84. digitalkin/grpc_servers/registry_server.py +0 -65
  85. digitalkin/grpc_servers/registry_servicer.py +0 -456
  86. digitalkin-0.3.1.dev1.dist-info/RECORD +0 -117
  87. {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.
@@ -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