dara-core 1.16.22__py3-none-any.whl → 1.17.0__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.
@@ -19,12 +19,14 @@ from typing import Any, List, Literal, Optional, Union
19
19
 
20
20
  from typing_extensions import TypedDict, TypeGuard
21
21
 
22
+ from dara.core.base_definitions import BaseTask, PendingTask
22
23
  from dara.core.interactivity import DataVariable, DerivedDataVariable, DerivedVariable
23
24
  from dara.core.interactivity.filtering import FilterQuery
24
25
  from dara.core.internal.cache_store import CacheStore
25
26
  from dara.core.internal.pandas_utils import remove_index
26
27
  from dara.core.internal.registry_lookup import RegistryLookup
27
28
  from dara.core.internal.tasks import TaskManager
29
+ from dara.core.logging import dev_logger
28
30
 
29
31
 
30
32
  class ResolvedDerivedVariable(TypedDict):
@@ -50,6 +52,14 @@ class ResolvedDataVariable(TypedDict):
50
52
  uid: str
51
53
 
52
54
 
55
+ class ResolvedSwitchVariable(TypedDict):
56
+ type: Literal['switch']
57
+ uid: str
58
+ value: Any
59
+ value_map: Any
60
+ default: Any
61
+
62
+
53
63
  def is_resolved_derived_variable(obj: Any) -> TypeGuard[ResolvedDerivedVariable]:
54
64
  return isinstance(obj, dict) and 'uid' in obj and obj.get('type') == 'derived'
55
65
 
@@ -62,8 +72,14 @@ def is_resolved_data_variable(obj: Any) -> TypeGuard[ResolvedDataVariable]:
62
72
  return isinstance(obj, dict) and 'uid' in obj and obj.get('type') == 'data'
63
73
 
64
74
 
75
+ def is_resolved_switch_variable(obj: Any) -> TypeGuard[ResolvedSwitchVariable]:
76
+ return isinstance(obj, dict) and 'uid' in obj and obj.get('type') == 'switch'
77
+
78
+
65
79
  async def resolve_dependency(
66
- entry: Union[ResolvedDerivedDataVariable, ResolvedDataVariable, ResolvedDerivedVariable, Any],
80
+ entry: Union[
81
+ ResolvedDerivedDataVariable, ResolvedDataVariable, ResolvedDerivedVariable, ResolvedSwitchVariable, Any
82
+ ],
67
83
  store: CacheStore,
68
84
  task_mgr: TaskManager,
69
85
  ):
@@ -84,6 +100,9 @@ async def resolve_dependency(
84
100
  if is_resolved_data_variable(entry):
85
101
  return await _resolve_data_var(entry, store)
86
102
 
103
+ if is_resolved_switch_variable(entry):
104
+ return await _resolve_switch_var(entry, store, task_mgr)
105
+
87
106
  return entry
88
107
 
89
108
 
@@ -149,3 +168,121 @@ async def _resolve_data_var(data_variable_entry: ResolvedDataVariable, store: Ca
149
168
  var = await registry_mgr.get(data_variable_registry, str(data_variable_entry.get('uid')))
150
169
  result = await DataVariable.get_value(var, store, data_variable_entry.get('filters', None))
151
170
  return remove_index(result)
171
+
172
+
173
+ def _normalize_lookup_key(value: Any) -> str:
174
+ """
175
+ Normalize a value to a string key that matches JavaScript object key serialization.
176
+ This ensures consistent lookup between Python backend and JavaScript frontend.
177
+
178
+ JavaScript's String() conversion rules:
179
+ - String(true) -> "true", String(false) -> "false"
180
+ - String(null) -> "null", String(undefined) -> "undefined"
181
+ - Numbers and other types are converted to their string representation
182
+
183
+ :param value: The value to normalize as a lookup key
184
+ :return: String representation suitable for object key lookup
185
+ """
186
+ if isinstance(value, bool):
187
+ # JavaScript String(true) -> "true", String(false) -> "false"
188
+ return str(value).lower()
189
+ elif value is None:
190
+ # JavaScript String(null) -> "null"
191
+ return 'null'
192
+ else:
193
+ # For numbers, strings, and other types, use standard string conversion
194
+ return str(value)
195
+
196
+
197
+ def _evaluate_condition(condition: dict) -> bool:
198
+ """
199
+ Evaluate a condition object and return the boolean result.
200
+
201
+ :param condition: condition dict with 'variable', 'operator', and 'other' keys
202
+ :return: boolean result of the condition evaluation
203
+ """
204
+ variable_value = condition['variable']
205
+ operator = condition['operator']
206
+ other_value = condition['other']
207
+
208
+ if operator == 'equal':
209
+ return variable_value == other_value
210
+ elif operator == 'truthy':
211
+ return bool(variable_value)
212
+ elif operator == 'not_equal':
213
+ return variable_value != other_value
214
+ else:
215
+ # strictly numeric comparisons, ensure they're numbers just in case
216
+ val_a = float(variable_value)
217
+ val_b = float(other_value)
218
+
219
+ if operator == 'greater_than':
220
+ return val_a > val_b
221
+ elif operator == 'greater_equal':
222
+ return val_a >= val_b
223
+ elif operator == 'less_than':
224
+ return val_a < val_b
225
+ elif operator == 'less_equal':
226
+ return val_a <= val_b
227
+ else:
228
+ raise ValueError(f'Unknown condition operator: {operator}')
229
+
230
+
231
+ async def _resolve_switch_var(switch_variable_entry: ResolvedSwitchVariable, store: CacheStore, task_mgr: TaskManager):
232
+ """
233
+ Resolve a switch variable by evaluating its constituent parts and returning the appropriate value.
234
+
235
+ :param switch_variable_entry: switch variable entry
236
+ :param store: store instance to use for caching
237
+ :param task_mgr: task manager instance
238
+ """
239
+
240
+ async def _resolve_maybe_task(value: Any) -> Any:
241
+ if isinstance(value, BaseTask):
242
+ task_result = await task_mgr.run_task(value)
243
+ if isinstance(task_result, PendingTask):
244
+ return await task_result.value()
245
+ return task_result
246
+ return value
247
+
248
+ # resolve the constituent parts
249
+ resolved_value = await resolve_dependency(switch_variable_entry.get('value'), store, task_mgr)
250
+ resolved_value = await _resolve_maybe_task(resolved_value)
251
+
252
+ resolved_value_map = await resolve_dependency(switch_variable_entry.get('value_map'), store, task_mgr)
253
+ resolved_value_map = await _resolve_maybe_task(resolved_value_map)
254
+
255
+ resolved_default = await resolve_dependency(switch_variable_entry.get('default'), store, task_mgr)
256
+ resolved_default = await _resolve_maybe_task(resolved_default)
257
+
258
+ # The frontend should have already evaluated conditions and sent the resolved value
259
+ # For switch variables, we just need to look up the value in the mapping
260
+ if isinstance(resolved_value_map, dict):
261
+ # value could be a condition object
262
+ if isinstance(resolved_value, dict) and resolved_value.get('__typename') == 'Condition':
263
+ # Evaluate the condition and use the boolean result as the lookup key
264
+ try:
265
+ # First, resolve any nested dependencies in the condition's 'other' field
266
+ condition_copy = resolved_value.copy()
267
+ condition_copy['other'] = await resolve_dependency(condition_copy.get('other'), store, task_mgr)
268
+ condition_copy['other'] = await _resolve_maybe_task(condition_copy['other'])
269
+
270
+ # Also resolve the variable field in case it's a dependency
271
+ condition_copy['variable'] = await resolve_dependency(condition_copy.get('variable'), store, task_mgr)
272
+ condition_copy['variable'] = await _resolve_maybe_task(condition_copy['variable'])
273
+
274
+ # Evaluate the condition
275
+ resolved_value = _evaluate_condition(condition_copy)
276
+ except Exception as e:
277
+ # If condition evaluation fails, log and use default
278
+ dev_logger.error('Error evaluating condition', error=e)
279
+ return resolved_default
280
+
281
+ # Normalize the lookup key to match JavaScript object key serialization
282
+ resolved_value = _normalize_lookup_key(resolved_value)
283
+
284
+ # Try to get the value from the mapping, fall back to default
285
+ return resolved_value_map.get(resolved_value, resolved_default)
286
+
287
+ # If value_map is not a dict (shouldn't happen in normal cases), return default
288
+ return resolved_default
dara/core/main.py CHANGED
@@ -314,6 +314,9 @@ def _start_application(config: Configuration):
314
314
  template_registry.register(name, renderer(config))
315
315
 
316
316
  except Exception as e:
317
+ import traceback
318
+
319
+ traceback.print_exc()
317
320
  dev_logger.error(
318
321
  'Something went wrong when building application template, there is most likely an issue in the application logic',
319
322
  e,