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.
- dara/core/__init__.py +3 -0
- dara/core/actions.py +1 -2
- dara/core/auth/basic.py +22 -16
- dara/core/auth/definitions.py +2 -2
- dara/core/auth/routes.py +5 -5
- dara/core/auth/utils.py +5 -5
- dara/core/base_definitions.py +22 -64
- dara/core/cli.py +8 -7
- dara/core/configuration.py +5 -2
- dara/core/css.py +1 -2
- dara/core/data_utils.py +18 -19
- dara/core/defaults.py +6 -7
- dara/core/definitions.py +50 -19
- dara/core/http.py +7 -3
- dara/core/interactivity/__init__.py +6 -0
- dara/core/interactivity/actions.py +52 -50
- dara/core/interactivity/any_data_variable.py +7 -134
- dara/core/interactivity/any_variable.py +5 -8
- dara/core/interactivity/client_variable.py +71 -0
- dara/core/interactivity/data_variable.py +8 -266
- dara/core/interactivity/derived_data_variable.py +7 -290
- dara/core/interactivity/derived_variable.py +416 -176
- dara/core/interactivity/filtering.py +46 -27
- dara/core/interactivity/loop_variable.py +2 -2
- dara/core/interactivity/non_data_variable.py +5 -68
- dara/core/interactivity/plain_variable.py +89 -15
- dara/core/interactivity/server_variable.py +325 -0
- dara/core/interactivity/state_variable.py +69 -0
- dara/core/interactivity/switch_variable.py +19 -19
- dara/core/interactivity/tabular_variable.py +94 -0
- dara/core/interactivity/url_variable.py +10 -90
- dara/core/internal/cache_store/base_impl.py +2 -1
- dara/core/internal/cache_store/cache_store.py +22 -25
- dara/core/internal/cache_store/keep_all.py +4 -1
- dara/core/internal/cache_store/lru.py +5 -1
- dara/core/internal/cache_store/ttl.py +4 -1
- dara/core/internal/cgroup.py +1 -1
- dara/core/internal/dependency_resolution.py +60 -66
- dara/core/internal/devtools.py +12 -5
- dara/core/internal/download.py +13 -4
- dara/core/internal/encoder_registry.py +7 -7
- dara/core/internal/execute_action.py +13 -13
- dara/core/internal/hashing.py +1 -3
- dara/core/internal/import_discovery.py +3 -4
- dara/core/internal/multi_resource_lock.py +70 -0
- dara/core/internal/normalization.py +9 -18
- dara/core/internal/pandas_utils.py +107 -5
- dara/core/internal/pool/definitions.py +1 -1
- dara/core/internal/pool/task_pool.py +25 -16
- dara/core/internal/pool/utils.py +21 -18
- dara/core/internal/pool/worker.py +3 -2
- dara/core/internal/port_utils.py +1 -1
- dara/core/internal/registries.py +12 -6
- dara/core/internal/registry.py +4 -2
- dara/core/internal/registry_lookup.py +11 -5
- dara/core/internal/routing.py +109 -145
- dara/core/internal/scheduler.py +13 -8
- dara/core/internal/settings.py +2 -2
- dara/core/internal/store.py +2 -29
- dara/core/internal/tasks.py +379 -195
- dara/core/internal/utils.py +36 -13
- dara/core/internal/websocket.py +21 -20
- dara/core/js_tooling/js_utils.py +28 -26
- dara/core/js_tooling/templates/vite.config.template.ts +12 -3
- dara/core/logging.py +13 -12
- dara/core/main.py +14 -11
- dara/core/metrics/cache.py +1 -1
- dara/core/metrics/utils.py +3 -3
- dara/core/persistence.py +27 -5
- dara/core/umd/dara.core.umd.js +68291 -64718
- dara/core/visual/components/__init__.py +2 -2
- dara/core/visual/components/fallback.py +30 -4
- dara/core/visual/components/for_cmp.py +4 -1
- dara/core/visual/css/__init__.py +30 -31
- dara/core/visual/dynamic_component.py +31 -28
- dara/core/visual/progress_updater.py +4 -3
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/METADATA +12 -11
- dara_core-1.20.1a3.dist-info/RECORD +119 -0
- dara_core-1.20.1a1.dist-info/RECORD +0 -114
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/LICENSE +0 -0
- {dara_core-1.20.1a1.dist-info → dara_core-1.20.1a3.dist-info}/WHEEL +0 -0
- {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
|
|
20
|
+
from typing import Optional, TypeVar
|
|
21
21
|
|
|
22
|
-
from pydantic import ConfigDict
|
|
22
|
+
from pydantic import ConfigDict
|
|
23
|
+
from typing_extensions import deprecated
|
|
23
24
|
|
|
24
|
-
from dara.core.interactivity.
|
|
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
|
-
|
|
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__(
|
|
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
|
|
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(
|
|
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
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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:
|
dara/core/internal/cgroup.py
CHANGED
|
@@ -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'
|
|
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
|
|
24
|
-
from dara.core.interactivity.
|
|
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
|
-
|
|
35
|
+
force_key: Optional[str]
|
|
38
36
|
|
|
39
37
|
|
|
40
|
-
class
|
|
41
|
-
|
|
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
|
|
68
|
-
return isinstance(obj, dict) and 'uid' in obj and obj.get('type') == '
|
|
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
|
-
|
|
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
|
|
101
|
-
return await
|
|
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,
|
|
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,
|
|
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
|
|
149
|
+
async def _resolve_server_var(resolved_server_variable: ResolvedServerVariable) -> Any:
|
|
158
150
|
"""
|
|
159
|
-
Resolve a
|
|
160
|
-
in.
|
|
151
|
+
Resolve a server variable.
|
|
161
152
|
|
|
162
|
-
:param
|
|
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
|
|
156
|
+
from dara.core.internal.registries import server_variable_registry, utils_registry
|
|
166
157
|
|
|
167
158
|
registry_mgr: RegistryLookup = utils_registry.get('RegistryLookup')
|
|
168
|
-
|
|
169
|
-
|
|
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(
|
|
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
|
|
dara/core/internal/devtools.py
CHANGED
|
@@ -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)
|
|
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)
|
|
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 {
|
|
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):
|
dara/core/internal/download.py
CHANGED
|
@@ -18,7 +18,9 @@ limitations under the License.
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import os
|
|
21
|
-
from
|
|
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[[
|
|
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
|
-
)
|
|
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(
|