dara-core 1.20.1a1__py3-none-any.whl → 1.20.1a3__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 (82) hide show
  1. dara/core/__init__.py +3 -0
  2. dara/core/actions.py +1 -2
  3. dara/core/auth/basic.py +22 -16
  4. dara/core/auth/definitions.py +2 -2
  5. dara/core/auth/routes.py +5 -5
  6. dara/core/auth/utils.py +5 -5
  7. dara/core/base_definitions.py +22 -64
  8. dara/core/cli.py +8 -7
  9. dara/core/configuration.py +5 -2
  10. dara/core/css.py +1 -2
  11. dara/core/data_utils.py +18 -19
  12. dara/core/defaults.py +6 -7
  13. dara/core/definitions.py +50 -19
  14. dara/core/http.py +7 -3
  15. dara/core/interactivity/__init__.py +6 -0
  16. dara/core/interactivity/actions.py +52 -50
  17. dara/core/interactivity/any_data_variable.py +7 -134
  18. dara/core/interactivity/any_variable.py +5 -8
  19. dara/core/interactivity/client_variable.py +71 -0
  20. dara/core/interactivity/data_variable.py +8 -266
  21. dara/core/interactivity/derived_data_variable.py +7 -290
  22. dara/core/interactivity/derived_variable.py +416 -176
  23. dara/core/interactivity/filtering.py +46 -27
  24. dara/core/interactivity/loop_variable.py +2 -2
  25. dara/core/interactivity/non_data_variable.py +5 -68
  26. dara/core/interactivity/plain_variable.py +89 -15
  27. dara/core/interactivity/server_variable.py +325 -0
  28. dara/core/interactivity/state_variable.py +69 -0
  29. dara/core/interactivity/switch_variable.py +19 -19
  30. dara/core/interactivity/tabular_variable.py +94 -0
  31. dara/core/interactivity/url_variable.py +10 -90
  32. dara/core/internal/cache_store/base_impl.py +2 -1
  33. dara/core/internal/cache_store/cache_store.py +22 -25
  34. dara/core/internal/cache_store/keep_all.py +4 -1
  35. dara/core/internal/cache_store/lru.py +5 -1
  36. dara/core/internal/cache_store/ttl.py +4 -1
  37. dara/core/internal/cgroup.py +1 -1
  38. dara/core/internal/dependency_resolution.py +60 -66
  39. dara/core/internal/devtools.py +12 -5
  40. dara/core/internal/download.py +13 -4
  41. dara/core/internal/encoder_registry.py +7 -7
  42. dara/core/internal/execute_action.py +13 -13
  43. dara/core/internal/hashing.py +1 -3
  44. dara/core/internal/import_discovery.py +3 -4
  45. dara/core/internal/multi_resource_lock.py +70 -0
  46. dara/core/internal/normalization.py +9 -18
  47. dara/core/internal/pandas_utils.py +107 -5
  48. dara/core/internal/pool/definitions.py +1 -1
  49. dara/core/internal/pool/task_pool.py +25 -16
  50. dara/core/internal/pool/utils.py +21 -18
  51. dara/core/internal/pool/worker.py +3 -2
  52. dara/core/internal/port_utils.py +1 -1
  53. dara/core/internal/registries.py +12 -6
  54. dara/core/internal/registry.py +4 -2
  55. dara/core/internal/registry_lookup.py +11 -5
  56. dara/core/internal/routing.py +109 -145
  57. dara/core/internal/scheduler.py +13 -8
  58. dara/core/internal/settings.py +2 -2
  59. dara/core/internal/store.py +2 -29
  60. dara/core/internal/tasks.py +379 -195
  61. dara/core/internal/utils.py +36 -13
  62. dara/core/internal/websocket.py +21 -20
  63. dara/core/js_tooling/js_utils.py +28 -26
  64. dara/core/js_tooling/templates/vite.config.template.ts +12 -3
  65. dara/core/logging.py +13 -12
  66. dara/core/main.py +14 -11
  67. dara/core/metrics/cache.py +1 -1
  68. dara/core/metrics/utils.py +3 -3
  69. dara/core/persistence.py +27 -5
  70. dara/core/umd/dara.core.umd.js +68291 -64718
  71. dara/core/visual/components/__init__.py +2 -2
  72. dara/core/visual/components/fallback.py +30 -4
  73. dara/core/visual/components/for_cmp.py +4 -1
  74. dara/core/visual/css/__init__.py +30 -31
  75. dara/core/visual/dynamic_component.py +31 -28
  76. dara/core/visual/progress_updater.py +4 -3
  77. {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/METADATA +12 -11
  78. dara_core-1.20.1a3.dist-info/RECORD +119 -0
  79. dara_core-1.20.1a1.dist-info/RECORD +0 -114
  80. {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/LICENSE +0 -0
  81. {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/WHEEL +0 -0
  82. {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/entry_points.txt +0 -0
@@ -17,16 +17,21 @@ limitations under the License.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- from typing import Any, Generic, Optional, TypeVar
20
+ from typing import Optional, TypeVar
21
21
 
22
- from pydantic import ConfigDict, SerializerFunctionWrapHandler, model_serializer
22
+ from pydantic import ConfigDict
23
+ from typing_extensions import deprecated
23
24
 
24
- from dara.core.interactivity.non_data_variable import NonDataVariable
25
+ from dara.core.interactivity.plain_variable import Variable
26
+ from dara.core.persistence import QueryParamStore
25
27
 
26
28
  VariableType = TypeVar('VariableType')
27
29
 
28
30
 
29
- class UrlVariable(NonDataVariable, Generic[VariableType]):
31
+ @deprecated(
32
+ 'UrlVariable is deprecated and will be removed in a future version. Use dara.core.interactivity.plain_variable.Variable with dara.core.persistence.QueryParamStore instead'
33
+ )
34
+ class UrlVariable(Variable[VariableType]):
30
35
  """
31
36
  A UrlVariable is very similar to a normal Variable however rather than it's state being stored in the memory of
32
37
  the client it's value is stored in the url of page as a query parameter. This is very useful for parameterizing
@@ -48,89 +53,4 @@ class UrlVariable(NonDataVariable, Generic[VariableType]):
48
53
  :param default: the initial value for the variable, defaults to None
49
54
  :param uid: the unique identifier for this variable; if not provided a random one is generated
50
55
  """
51
- super().__init__(query=query, default=default, uid=uid)
52
-
53
- def sync(self):
54
- """
55
- Create an action to synchronise the value of this UrlVariable with input value sent from the component.
56
-
57
- ```python
58
-
59
- from dara.core import UrlVariable
60
- from dara.components import Select
61
-
62
- var = UrlVariable('first', query='num')
63
- another_var = UrlVariable('second', query='num_two')
64
-
65
- Select(
66
- value=var,
67
- items=['first', 'second', 'third'],
68
- onchange=another_var.sync(),
69
- )
70
-
71
- ```
72
- """
73
- from dara.core.interactivity.actions import (
74
- UpdateVariableImpl,
75
- assert_no_context,
76
- )
77
-
78
- assert_no_context('ctx.update')
79
- return UpdateVariableImpl(variable=self, value=UpdateVariableImpl.INPUT)
80
-
81
- def toggle(self):
82
- """
83
- Create an action to toggle the value of this UrlVariable. Note this only works for boolean variables.
84
-
85
- ```python
86
-
87
- from dara.core import UrlVariable
88
- from dara.components import Button
89
-
90
- var = UrlVariable(True, query='show')
91
-
92
- Button(
93
- 'Toggle',
94
- onclick=var.toggle(),
95
- )
96
-
97
- ```
98
- """
99
- from dara.core.interactivity.actions import (
100
- UpdateVariableImpl,
101
- assert_no_context,
102
- )
103
-
104
- assert_no_context('ctx.update')
105
- return UpdateVariableImpl(variable=self, value=UpdateVariableImpl.TOGGLE)
106
-
107
- def update(self, value: Any):
108
- """
109
- Create an action to update the value of this UrlVariable to a provided value.
110
-
111
- ```python
112
-
113
- from dara.core import UrlVariable
114
- from dara.components import Button
115
-
116
- show = UrlVariable(True, query='show')
117
-
118
- Button(
119
- 'Hide',
120
- onclick=show.update(False),
121
- )
122
-
123
- ```
124
- """
125
- from dara.core.interactivity.actions import (
126
- UpdateVariableImpl,
127
- assert_no_context,
128
- )
129
-
130
- assert_no_context('ctx.update')
131
- return UpdateVariableImpl(variable=self, value=value)
132
-
133
- @model_serializer(mode='wrap')
134
- def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict:
135
- parent_dict = nxt(self)
136
- return {**parent_dict, '__typename': 'UrlVariable', 'uid': str(parent_dict['uid'])}
56
+ super().__init__(default=default, uid=uid, store=QueryParamStore(query=query), query=query)
@@ -19,12 +19,13 @@ class CacheStoreImpl(abc.ABC, Generic[PolicyT]):
19
19
  """
20
20
 
21
21
  @abc.abstractmethod
22
- async def get(self, key: str, unpin: bool = False) -> Any:
22
+ async def get(self, key: str, unpin: bool = False, raise_for_missing: bool = False) -> Any:
23
23
  """
24
24
  Retrieve an entry from the cache.
25
25
 
26
26
  :param key: The key of the entry to retrieve.
27
27
  :param unpin: If true, the entry will be unpinned if it is pinned.
28
+ :param raise_for_missing: If true, an exception will be raised if the entry is not found
28
29
  """
29
30
 
30
31
  @abc.abstractmethod
@@ -6,7 +6,6 @@ from dara.core.base_definitions import (
6
6
  LruCachePolicy,
7
7
  MostRecentCachePolicy,
8
8
  PendingTask,
9
- PendingValue,
10
9
  TTLCachePolicy,
11
10
  )
12
11
  from dara.core.internal.cache_store.base_impl import CacheStoreImpl, PolicyT
@@ -23,7 +22,7 @@ def cache_impl_for_policy(policy: PolicyT) -> CacheStoreImpl[PolicyT]:
23
22
  """
24
23
  impl: Optional[CacheStoreImpl] = None
25
24
 
26
- if isinstance(policy, LruCachePolicy) or isinstance(policy, MostRecentCachePolicy):
25
+ if isinstance(policy, (LruCachePolicy, MostRecentCachePolicy)):
27
26
  impl = LRUCache(policy)
28
27
  elif isinstance(policy, TTLCachePolicy):
29
28
  impl = TTLCache(policy)
@@ -62,21 +61,24 @@ class CacheScopeStore(Generic[PolicyT]):
62
61
 
63
62
  return await cache.delete(key)
64
63
 
65
- async def get(self, key: str, unpin: bool = False) -> Optional[Any]:
64
+ async def get(self, key: str, unpin: bool = False, raise_for_missing: bool = False) -> Optional[Any]:
66
65
  """
67
66
  Retrieve an entry from the cache.
68
67
 
69
68
  :param key: The key of the entry to retrieve.
70
69
  :param unpin: If true, the entry will be unpinned if it is pinned.
70
+ :param raise_for_missing: If true, an exception will be raised if the entry is not found
71
71
  """
72
72
  scope = get_cache_scope(self.policy.cache_type)
73
73
  cache = self.caches.get(scope)
74
74
 
75
75
  # No cache for this scope yet
76
76
  if cache is None:
77
+ if raise_for_missing:
78
+ raise KeyError(f'No cache found for {scope}')
77
79
  return None
78
80
 
79
- return await cache.get(key, unpin=unpin)
81
+ return await cache.get(key, unpin=unpin, raise_for_missing=raise_for_missing)
80
82
 
81
83
  async def set(self, key: str, value: Any, pin: bool = False):
82
84
  """
@@ -150,21 +152,30 @@ class CacheStore:
150
152
 
151
153
  return prev_entry
152
154
 
153
- async def get(self, registry_entry: CachedRegistryEntry, key: str, unpin: bool = False) -> Optional[Any]:
155
+ async def get(
156
+ self,
157
+ registry_entry: CachedRegistryEntry,
158
+ key: str,
159
+ unpin: bool = False,
160
+ raise_for_missing: bool = False,
161
+ ) -> Optional[Any]:
154
162
  """
155
163
  Retrieve an entry from the cache for the given registry entry and cache key.
156
164
 
157
165
  :param registry_entry: The registry entry to retrieve the value for.
158
166
  :param key: The key of the entry to retrieve.
159
167
  :param unpin: If true, the entry will be unpinned if it is pinned.
168
+ :param raise_for_missing: If true, an exception will be raised if the entry is not found
160
169
  """
161
170
  registry_store = self.registry_stores.get(registry_entry.to_store_key())
162
171
 
163
172
  # No store for this entry yet
164
173
  if registry_store is None:
174
+ if raise_for_missing:
175
+ raise KeyError(f'No cache store found for {registry_entry.to_store_key()}')
165
176
  return None
166
177
 
167
- return await registry_store.get(key, unpin=unpin)
178
+ return await registry_store.get(key, unpin=unpin, raise_for_missing=raise_for_missing)
168
179
 
169
180
  async def get_or_wait(self, registry_entry: CachedRegistryEntry, key: str):
170
181
  """
@@ -177,9 +188,6 @@ class CacheStore:
177
188
 
178
189
  value = await self.get(registry_entry, key)
179
190
 
180
- if isinstance(value, PendingValue):
181
- return await value.wait()
182
-
183
191
  if isinstance(value, PendingTask):
184
192
  return await value.run()
185
193
 
@@ -190,7 +198,6 @@ class CacheStore:
190
198
  registry_entry: CachedRegistryEntry,
191
199
  key: str,
192
200
  value: Any,
193
- error: Optional[Exception] = None,
194
201
  pin: bool = False,
195
202
  ):
196
203
  """
@@ -213,12 +220,11 @@ class CacheStore:
213
220
 
214
221
  prev_value = await registry_store.get(key)
215
222
 
216
- # If previous value was a PendingValue, resolve it
217
- if isinstance(prev_value, PendingValue):
218
- if error is not None:
219
- prev_value.error(error)
220
- else:
221
- prev_value.resolve(value)
223
+ # If the previous value was a PendingTask, resolve it with the new value
224
+ # This handles cache-coordinated tasks (e.g., DerivedVariables) where PendingTasks
225
+ # are stored in cache to coordinate multiple callers with the same cache key
226
+ if isinstance(prev_value, PendingTask):
227
+ prev_value.resolve(value)
222
228
 
223
229
  # Update size
224
230
  self._update_size(prev_value, value)
@@ -228,15 +234,6 @@ class CacheStore:
228
234
 
229
235
  return value
230
236
 
231
- async def set_pending(self, registry_entry: CachedRegistryEntry, key: str):
232
- """
233
- Set a pending value for the given registry entry and cache key.
234
-
235
- :param registry_entry: The registry entry to store the value for.
236
- :param key: The key of the entry to set.
237
- """
238
- return await self.set(registry_entry, key, PendingValue())
239
-
240
237
  async def clear(self):
241
238
  """
242
239
  Empty all stores.
@@ -42,18 +42,21 @@ class KeepAllCache(CacheStoreImpl[KeepAllCachePolicy]):
42
42
  del self.cache[key]
43
43
  return entry
44
44
 
45
- async def get(self, key: str, unpin: bool = False) -> Optional[Any]:
45
+ async def get(self, key: str, unpin: bool = False, raise_for_missing: bool = False) -> Optional[Any]:
46
46
  """
47
47
  Retrieve a value from the cache.
48
48
 
49
49
  :param key: The key of the value to retrieve.
50
50
  :param unpin: This parameter is ignored in KeepAllCache as entries are never evicted.
51
+ :param raise_for_missing: If true, an exception will be raised if the entry is not found
51
52
  :return: The value associated with the key, or None if the key is not in the cache.
52
53
  """
53
54
  async with self.lock:
54
55
  entry = self.cache.get(key)
55
56
 
56
57
  if entry is None:
58
+ if raise_for_missing:
59
+ raise KeyError(f'No cache entry found for {key}')
57
60
  return None
58
61
 
59
62
  if unpin:
@@ -88,12 +88,13 @@ class LRUCache(CacheStoreImpl[LruCachePolicy]):
88
88
  self.cache.pop(key, None)
89
89
  return node.value
90
90
 
91
- async def get(self, key: str, unpin: bool = False) -> Optional[Any]:
91
+ async def get(self, key: str, unpin: bool = False, raise_for_missing: bool = False) -> Optional[Any]:
92
92
  """
93
93
  Retrieve a value from the cache.
94
94
 
95
95
  :param key: The key of the value to retrieve.
96
96
  :param unpin: If true, the entry will be unpinned if it is pinned.
97
+ :param raise_for_missing: If true, an exception will be raised if the entry is not found
97
98
  :return: The value associated with the key, or None if the key is not in the cache.
98
99
  """
99
100
  async with self.lock:
@@ -103,6 +104,9 @@ class LRUCache(CacheStoreImpl[LruCachePolicy]):
103
104
  node.pin = False
104
105
  self._move_to_front(node)
105
106
  return node.value
107
+
108
+ if raise_for_missing:
109
+ raise KeyError(f'No cache entry found for {key}')
106
110
  return None
107
111
 
108
112
  async def set(self, key: str, value: Any, pin: bool = False) -> None:
@@ -52,12 +52,13 @@ class TTLCache(CacheStoreImpl[TTLCachePolicy]):
52
52
  _, key = heapq.heappop(self.expiration_heap)
53
53
  self.unpinned_cache.pop(key, None)
54
54
 
55
- async def get(self, key: str, unpin: bool = False) -> Any:
55
+ async def get(self, key: str, unpin: bool = False, raise_for_missing: bool = False) -> Any:
56
56
  """
57
57
  Retrieve a value from the cache.
58
58
 
59
59
  :param key: The key of the value to retrieve.
60
60
  :param unpin: If true, the entry will be unpinned if it is pinned.
61
+ :param raise_for_missing: If true, an exception will be raised if the entry is not found
61
62
  :return: The value associated with the key, or None if the key is not in the cache.
62
63
  """
63
64
  async with self.lock:
@@ -75,6 +76,8 @@ class TTLCache(CacheStoreImpl[TTLCachePolicy]):
75
76
  elif key in self.unpinned_cache:
76
77
  return self.unpinned_cache[key].value
77
78
 
79
+ if raise_for_missing:
80
+ raise KeyError(f'No cache entry found for {key}')
78
81
  return None
79
82
 
80
83
  async def set(self, key: str, value: Any, pin: bool = False) -> None:
@@ -22,7 +22,7 @@ import sys
22
22
 
23
23
  from dara.core.logging import dev_logger, eng_logger
24
24
 
25
- CGROUP_V2_INDICATOR_PATH = '/sys/fs/cgroup/cgroup.controllers' # Used to determine whether we're using cgroupv2
25
+ CGROUP_V2_INDICATOR_PATH = '/sys/fs/cgroup/cgroup.controllers' # Used to determine whether we're using cgroupv2
26
26
 
27
27
  CGROUP_V1_MEM_PATH = '/sys/fs/cgroup/memory/memory.limit_in_bytes'
28
28
  CGROUP_V2_MEM_PATH = '/sys/fs/cgroup/memory.max'
@@ -20,36 +20,25 @@ from typing import Any, List, Literal, Optional, Union
20
20
  from typing_extensions import TypedDict, TypeGuard
21
21
 
22
22
  from dara.core.base_definitions import BaseTask, PendingTask
23
- from dara.core.interactivity import DataVariable, DerivedDataVariable, DerivedVariable
24
- from dara.core.interactivity.filtering import FilterQuery
23
+ from dara.core.interactivity import DerivedVariable
24
+ from dara.core.interactivity.server_variable import ServerVariable
25
25
  from dara.core.internal.cache_store import CacheStore
26
- from dara.core.internal.pandas_utils import remove_index
27
26
  from dara.core.internal.registry_lookup import RegistryLookup
28
27
  from dara.core.internal.tasks import TaskManager
29
28
  from dara.core.logging import dev_logger
30
29
 
31
30
 
32
31
  class ResolvedDerivedVariable(TypedDict):
33
- deps: List[int]
34
32
  type: Literal['derived']
35
33
  uid: str
36
34
  values: List[Any]
37
- force: bool
35
+ force_key: Optional[str]
38
36
 
39
37
 
40
- class ResolvedDerivedDataVariable(TypedDict):
41
- deps: List[int]
42
- type: Literal['derived-data']
43
- uid: str
44
- values: List[Any]
45
- filters: Optional[Union[FilterQuery, dict]]
46
- force: bool
47
-
48
-
49
- class ResolvedDataVariable(TypedDict):
50
- filters: Optional[Union[FilterQuery, dict]]
51
- type: Literal['data']
38
+ class ResolvedServerVariable(TypedDict):
39
+ type: Literal['server']
52
40
  uid: str
41
+ sequence_number: int
53
42
 
54
43
 
55
44
  class ResolvedSwitchVariable(TypedDict):
@@ -64,21 +53,47 @@ def is_resolved_derived_variable(obj: Any) -> TypeGuard[ResolvedDerivedVariable]
64
53
  return isinstance(obj, dict) and 'uid' in obj and obj.get('type') == 'derived'
65
54
 
66
55
 
67
- def is_resolved_derived_data_variable(obj: Any) -> TypeGuard[ResolvedDerivedDataVariable]:
68
- return isinstance(obj, dict) and 'uid' in obj and obj.get('type') == 'derived-data'
69
-
70
-
71
- def is_resolved_data_variable(obj: Any) -> TypeGuard[ResolvedDataVariable]:
72
- return isinstance(obj, dict) and 'uid' in obj and obj.get('type') == 'data'
56
+ def is_resolved_server_variable(obj: Any) -> TypeGuard[ResolvedServerVariable]:
57
+ return isinstance(obj, dict) and 'uid' in obj and obj.get('type') == 'server'
73
58
 
74
59
 
75
60
  def is_resolved_switch_variable(obj: Any) -> TypeGuard[ResolvedSwitchVariable]:
76
61
  return isinstance(obj, dict) and 'uid' in obj and obj.get('type') == 'switch'
77
62
 
78
63
 
64
+ def is_forced(value: Any) -> bool:
65
+ """
66
+ Whether a value is a Derived(Data)Variable with a force_key or any of its values are forced
67
+ """
68
+ if not is_resolved_derived_variable(value):
69
+ return False
70
+
71
+ return value.get('force_key') is not None or any(is_forced(v) for v in value.get('values', []))
72
+
73
+
74
+ def clean_force_key(value: Any) -> Any:
75
+ """
76
+ Clean an argument to a value to remove force keys
77
+ """
78
+ if value is None:
79
+ return value
80
+
81
+ if isinstance(value, dict):
82
+ # clone the dict to avoid mutating the original
83
+ value = value.copy()
84
+ # Remove force key from the value
85
+ value.pop('force_key', None)
86
+ return {k: clean_force_key(v) for k, v in value.items()}
87
+ if isinstance(value, list):
88
+ return [clean_force_key(v) for v in value]
89
+ return value
90
+
91
+
79
92
  async def resolve_dependency(
80
93
  entry: Union[
81
- ResolvedDerivedDataVariable, ResolvedDataVariable, ResolvedDerivedVariable, ResolvedSwitchVariable, Any
94
+ ResolvedDerivedVariable,
95
+ ResolvedSwitchVariable,
96
+ Any,
82
97
  ],
83
98
  store: CacheStore,
84
99
  task_mgr: TaskManager,
@@ -91,14 +106,11 @@ async def resolve_dependency(
91
106
  :param store: store instance
92
107
  :param task_mgr: task manager instance
93
108
  """
94
- if is_resolved_derived_data_variable(entry):
95
- return await _resolve_derived_data_var(entry, store, task_mgr)
96
-
97
109
  if is_resolved_derived_variable(entry):
98
110
  return await _resolve_derived_var(entry, store, task_mgr)
99
111
 
100
- if is_resolved_data_variable(entry):
101
- return await _resolve_data_var(entry, store)
112
+ if is_resolved_server_variable(entry):
113
+ return await _resolve_server_var(entry)
102
114
 
103
115
  if is_resolved_switch_variable(entry):
104
116
  return await _resolve_switch_var(entry, store, task_mgr)
@@ -106,34 +118,10 @@ async def resolve_dependency(
106
118
  return entry
107
119
 
108
120
 
109
- async def _resolve_derived_data_var(entry: ResolvedDerivedDataVariable, store: CacheStore, task_mgr: TaskManager):
110
- """
111
- Resolve a derived data variable from the registry
112
-
113
- :param entry: derived data variable entry
114
- :param store: store instance to use for caching
115
- :param task_mgr: task manager instance
116
- """
117
- from dara.core.internal.registries import (
118
- data_variable_registry,
119
- derived_variable_registry,
120
- utils_registry,
121
- )
122
-
123
- registry_mgr: RegistryLookup = utils_registry.get('RegistryLookup')
124
- dv_var = await registry_mgr.get(derived_variable_registry, str(entry.get('uid')))
125
- data_var = await registry_mgr.get(data_variable_registry, str(entry.get('uid')))
126
-
127
- input_values: List[Any] = entry.get('values', [])
128
-
129
- result = await DerivedDataVariable.resolve_value(
130
- data_var, dv_var, store, task_mgr, input_values, entry.get('force', False), entry.get('filters', None)
131
- )
132
- return remove_index(result)
133
-
134
-
135
121
  async def _resolve_derived_var(
136
- derived_variable_entry: ResolvedDerivedVariable, store: CacheStore, task_mgr: TaskManager
122
+ derived_variable_entry: ResolvedDerivedVariable,
123
+ store: CacheStore,
124
+ task_mgr: TaskManager,
137
125
  ):
138
126
  """
139
127
  Resolve a derived variable from the registry and get it's new value based on the dynamic variable mapping passed
@@ -149,25 +137,27 @@ async def _resolve_derived_var(
149
137
  var = await registry_mgr.get(derived_variable_registry, str(derived_variable_entry.get('uid')))
150
138
  input_values: List[Any] = derived_variable_entry.get('values', [])
151
139
  result = await DerivedVariable.get_value(
152
- var, store, task_mgr, input_values, derived_variable_entry.get('force', False)
140
+ var_entry=var,
141
+ store=store,
142
+ task_mgr=task_mgr,
143
+ args=input_values,
144
+ force_key=derived_variable_entry.get('force_key'),
153
145
  )
154
146
  return result['value']
155
147
 
156
148
 
157
- async def _resolve_data_var(data_variable_entry: ResolvedDataVariable, store: CacheStore):
149
+ async def _resolve_server_var(resolved_server_variable: ResolvedServerVariable) -> Any:
158
150
  """
159
- Resolve a data variable from the registry and get it's new value based on the dynamic variable mapping passed
160
- in.
151
+ Resolve a server variable.
161
152
 
162
- :param data_variable_entry: data var entry
153
+ :param server_variable_entry: server var entry
163
154
  :param store: the store instance to use for caching
164
155
  """
165
- from dara.core.internal.registries import data_variable_registry, utils_registry
156
+ from dara.core.internal.registries import server_variable_registry, utils_registry
166
157
 
167
158
  registry_mgr: RegistryLookup = utils_registry.get('RegistryLookup')
168
- var = await registry_mgr.get(data_variable_registry, str(data_variable_entry.get('uid')))
169
- result = await DataVariable.get_value(var, store, data_variable_entry.get('filters', None))
170
- return remove_index(result)
159
+ server_var_entry = await registry_mgr.get(server_variable_registry, resolved_server_variable['uid'])
160
+ return await ServerVariable.get_value(server_var_entry)
171
161
 
172
162
 
173
163
  def _normalize_lookup_key(value: Any) -> str:
@@ -228,7 +218,11 @@ def _evaluate_condition(condition: dict) -> bool:
228
218
  raise ValueError(f'Unknown condition operator: {operator}')
229
219
 
230
220
 
231
- async def _resolve_switch_var(switch_variable_entry: ResolvedSwitchVariable, store: CacheStore, task_mgr: TaskManager):
221
+ async def _resolve_switch_var(
222
+ switch_variable_entry: ResolvedSwitchVariable,
223
+ store: CacheStore,
224
+ task_mgr: TaskManager,
225
+ ):
232
226
  """
233
227
  Resolve a switch variable by evaluating its constituent parts and returning the appropriate value.
234
228
 
@@ -21,22 +21,26 @@ import sys
21
21
  import traceback
22
22
  from contextlib import contextmanager
23
23
  from datetime import datetime
24
+ from typing import Optional
24
25
 
25
26
  from dara.core.internal.websocket import WebsocketManager
26
27
  from dara.core.logging import eng_logger
27
28
 
28
29
 
29
- def print_stacktrace():
30
+ def print_stacktrace(err: Optional[BaseException] = None) -> str:
30
31
  """
31
32
  Prints out the current stack trace. Will also extract any exceptions and print them at the end.
32
33
  """
34
+ if err is not None:
35
+ return ''.join(traceback.format_exception(type(err), err, err.__traceback__))
36
+
33
37
  exc = sys.exc_info()[0]
34
38
  stack = traceback.extract_stack()[:-1]
35
39
 
36
40
  trc = 'Traceback (most recent call last):\n'
37
41
  stackstr = trc + ''.join(traceback.format_list(stack))
38
42
  if exc is not None:
39
- stackstr += ' ' + traceback.format_exc().lstrip(trc) # pylint:disable=bad-str-strip-call
43
+ stackstr += ' ' + traceback.format_exc().lstrip(trc)
40
44
  else:
41
45
  stackstr += ' Exception'
42
46
 
@@ -52,14 +56,17 @@ def handle_system_exit(error_msg: str):
52
56
  try:
53
57
  yield
54
58
  except SystemExit as e:
55
- raise InterruptedError(error_msg).with_traceback(e.__traceback__)
59
+ raise InterruptedError(error_msg) from e
56
60
 
57
61
 
58
- def get_error_for_channel() -> dict:
62
+ def get_error_for_channel(err: Optional[BaseException] = None) -> dict:
59
63
  """
60
64
  Get error from current stacktrace to send to the client
61
65
  """
62
- return {'error': print_stacktrace(), 'time': str(datetime.now())}
66
+ return {
67
+ 'error': print_stacktrace(err),
68
+ 'time': str(datetime.now()),
69
+ }
63
70
 
64
71
 
65
72
  async def send_error_for_session(ws_mgr: WebsocketManager, session_id: str):
@@ -18,7 +18,9 @@ limitations under the License.
18
18
  from __future__ import annotations
19
19
 
20
20
  import os
21
- from typing import Awaitable, Callable, Optional, Tuple
21
+ from collections.abc import Awaitable
22
+ from contextvars import ContextVar
23
+ from typing import Callable, Optional, Tuple
22
24
  from uuid import uuid4
23
25
 
24
26
  import anyio
@@ -37,13 +39,13 @@ class DownloadDataEntry(BaseModel):
37
39
  file_path: str
38
40
  cleanup_file: bool
39
41
  identity_name: Optional[str] = None
40
- download: Callable[['DownloadDataEntry'], Awaitable[Tuple[anyio.AsyncFile, Callable[..., Awaitable]]]]
42
+ download: Callable[[DownloadDataEntry], Awaitable[Tuple[anyio.AsyncFile, Callable[..., Awaitable]]]]
41
43
  """Handler for getting the file from the entry"""
42
44
 
43
45
 
44
46
  DownloadRegistryEntry = CachedRegistryEntry(
45
47
  uid='_dara_download', cache=Cache.Policy.TTL(ttl=60 * 10)
46
- ) # expire the codes after 10 minutes
48
+ ) # expire the codes after 10 minutes
47
49
 
48
50
 
49
51
  async def download(data_entry: DownloadDataEntry) -> Tuple[anyio.AsyncFile, Callable[..., Awaitable]]:
@@ -72,6 +74,13 @@ async def download(data_entry: DownloadDataEntry) -> Tuple[anyio.AsyncFile, Call
72
74
  return (async_file, cleanup)
73
75
 
74
76
 
77
+ GENERATE_CODE_OVERRIDE = ContextVar[Optional[Callable[[str], str]]]('GENERATE_CODE_OVERRIDE', default=None)
78
+ """
79
+ Optional context variable which can be used to override the default behaviour of code generation.
80
+ Invoked with the file path to generate a download code for.
81
+ """
82
+
83
+
75
84
  async def generate_download_code(file_path: str, cleanup_file: bool) -> str:
76
85
  """
77
86
  Generate a one-time download code for a given dataset.
@@ -87,7 +96,7 @@ async def generate_download_code(file_path: str, cleanup_file: bool) -> str:
87
96
  user = USER.get()
88
97
 
89
98
  # Unique download id
90
- uid = str(uuid4())
99
+ uid = override(file_path) if (override := GENERATE_CODE_OVERRIDE.get()) else str(uuid4())
91
100
 
92
101
  # Put it in the store under the registry entry with TTL configured
93
102
  await store.set(