dara-core 1.20.3__py3-none-any.whl → 1.21.1__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 (33) hide show
  1. dara/core/__init__.py +8 -42
  2. dara/core/configuration.py +33 -4
  3. dara/core/defaults.py +7 -0
  4. dara/core/definitions.py +22 -35
  5. dara/core/interactivity/actions.py +29 -28
  6. dara/core/interactivity/plain_variable.py +6 -2
  7. dara/core/interactivity/switch_variable.py +2 -2
  8. dara/core/internal/execute_action.py +75 -6
  9. dara/core/internal/routing.py +526 -354
  10. dara/core/internal/tasks.py +1 -1
  11. dara/core/jinja/index.html +97 -1
  12. dara/core/jinja/index_autojs.html +116 -10
  13. dara/core/js_tooling/js_utils.py +35 -14
  14. dara/core/main.py +137 -89
  15. dara/core/persistence.py +6 -2
  16. dara/core/router/__init__.py +5 -0
  17. dara/core/router/compat.py +77 -0
  18. dara/core/router/components.py +143 -0
  19. dara/core/router/dependency_graph.py +62 -0
  20. dara/core/router/router.py +887 -0
  21. dara/core/umd/{dara.core.umd.js → dara.core.umd.cjs} +62588 -46966
  22. dara/core/umd/style.css +52 -9
  23. dara/core/visual/components/__init__.py +16 -11
  24. dara/core/visual/components/menu.py +4 -0
  25. dara/core/visual/components/menu_link.py +1 -0
  26. dara/core/visual/components/powered_by_causalens.py +9 -0
  27. dara/core/visual/components/sidebar_frame.py +1 -0
  28. dara/core/visual/dynamic_component.py +1 -1
  29. {dara_core-1.20.3.dist-info → dara_core-1.21.1.dist-info}/METADATA +10 -10
  30. {dara_core-1.20.3.dist-info → dara_core-1.21.1.dist-info}/RECORD +33 -26
  31. {dara_core-1.20.3.dist-info → dara_core-1.21.1.dist-info}/LICENSE +0 -0
  32. {dara_core-1.20.3.dist-info → dara_core-1.21.1.dist-info}/WHEEL +0 -0
  33. {dara_core-1.20.3.dist-info → dara_core-1.21.1.dist-info}/entry_points.txt +0 -0
@@ -20,7 +20,8 @@ from __future__ import annotations
20
20
  import asyncio
21
21
  from collections.abc import Mapping
22
22
  from contextvars import ContextVar
23
- from typing import Any, Callable, Optional, Union
23
+ from functools import partial
24
+ from typing import Any, Callable, Literal, Optional, Union
24
25
 
25
26
  import anyio
26
27
 
@@ -42,13 +43,16 @@ from dara.core.logging import dev_logger
42
43
  CURRENT_ACTION_ID = ContextVar('current_action_id', default='')
43
44
 
44
45
 
45
- async def _execute_action(handler: Callable, ctx: ActionCtx, values: Mapping[str, Any]):
46
+ async def _execute_action(
47
+ handler: Callable, ctx: ActionCtx, values: Mapping[str, Any], _on_error: Literal['raise', 'notify'] = 'notify'
48
+ ):
46
49
  """
47
50
  Execute the action handler within the given action context, handling any exceptions that occur.
48
51
 
49
52
  :param handler: the action handler to execute
50
53
  :param ctx: the action context to use
51
54
  :param values: the resolved values to pass to the handler
55
+ :param _on_error: whether to raise or notify on errors
52
56
  """
53
57
  bound_arg = None
54
58
  kwarg_names = list(values.keys())
@@ -71,13 +75,18 @@ async def _execute_action(handler: Callable, ctx: ActionCtx, values: Mapping[str
71
75
  try:
72
76
  return await run_user_handler(handler, args=args, kwargs=parsed_values)
73
77
  except Exception as e:
74
- dev_logger.error('Error executing action', e)
75
- await ctx.notify('An error occurred while executing the action', 'Error', 'ERROR')
78
+ if _on_error == 'raise':
79
+ raise
80
+ elif _on_error == 'notify':
81
+ dev_logger.error('Error executing action', e)
82
+ await ctx.notify('An error occurred while executing the action', 'Error', 'ERROR')
76
83
  finally:
77
84
  await ctx._end_execution()
78
85
 
79
86
 
80
- async def _stream_action(handler: Callable, ctx: ActionCtx, **values: Mapping[str, Any]):
87
+ async def _stream_action(
88
+ handler: Callable, ctx: ActionCtx, _on_error: Literal['raise', 'notify'] = 'notify', **values: Mapping[str, Any]
89
+ ):
81
90
  """
82
91
  Run the action handler and stream the results to the frontend.
83
92
  Executes two tasks in parallel:
@@ -91,13 +100,73 @@ async def _stream_action(handler: Callable, ctx: ActionCtx, **values: Mapping[st
91
100
  try:
92
101
  async with anyio.create_task_group() as tg:
93
102
  # Execute the handler and a stream consumer in parallel
94
- tg.start_soon(_execute_action, handler, ctx, values)
103
+ tg.start_soon(partial(_execute_action, _on_error=_on_error), handler, ctx, values)
95
104
  tg.start_soon(ctx._handle_results)
96
105
  finally:
97
106
  # None is treated as a sentinel value to stop waiting for new actions to come in on the client
98
107
  await ctx._on_action(None)
99
108
 
100
109
 
110
+ async def execute_action_sync(
111
+ action_def: ActionResolverDef,
112
+ inp: Any,
113
+ values: Mapping[str, Any],
114
+ static_kwargs: Mapping[str, Any],
115
+ store: CacheStore,
116
+ task_mgr: TaskManager,
117
+ ):
118
+ """
119
+ Execute an action until completion.
120
+ Used for executing `on_load` route actions.
121
+
122
+ :param action_def: resolver definition
123
+ :param inp: input to the action
124
+ :param values: values from the frontend
125
+ :param static_kwargs: mapping of var names to current values for static arguments
126
+ :param store: store instance
127
+ :param task_mgr: task manager instance - task are not supported here but passed for compat
128
+ """
129
+ action = action_def.resolver
130
+ assert action is not None, 'Action resolver must be defined'
131
+
132
+ results = []
133
+
134
+ # Construct a context which handles action messages by accumulating them in an array
135
+ async def handle_action(act_impl: Optional[ActionImpl]):
136
+ if act_impl is not None:
137
+ results.append(act_impl)
138
+
139
+ ctx = ActionCtx(inp, handle_action)
140
+ ACTION_CONTEXT.set(ctx)
141
+
142
+ resolved_kwargs = {}
143
+
144
+ if values is not None:
145
+ annotations = action.__annotations__
146
+
147
+ async def _resolve_kwarg(val: Any, key: str):
148
+ typ = annotations.get(key)
149
+ val = await resolve_dependency(val, store, task_mgr)
150
+ resolved_kwargs[key] = deserialize(val, typ)
151
+
152
+ async with anyio.create_task_group() as tg:
153
+ for key, value in values.items():
154
+ tg.start_soon(_resolve_kwarg, value, key)
155
+
156
+ # Merge resolved dynamic kwargs with static kwargs received
157
+ resolved_kwargs = {**resolved_kwargs, **static_kwargs}
158
+
159
+ # Disallow tasks here
160
+ has_tasks = any(isinstance(extra, BaseTask) for extra in resolved_kwargs.values())
161
+ if has_tasks:
162
+ raise ValueError('This action does not support tasks')
163
+
164
+ # Run until completion, raising on errors
165
+ await _stream_action(action, ctx, _on_error='raise', **resolved_kwargs)
166
+
167
+ return results
168
+
169
+
101
170
  async def execute_action(
102
171
  action_def: ActionResolverDef,
103
172
  inp: Any,